From 27341c8a53488bd9d5faafa3def2ba1a70a51c9c Mon Sep 17 00:00:00 2001 From: Yuhang Wu Date: Thu, 30 Jul 2026 14:08:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=E5=BC=80?= =?UTF-8?q?=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本次提交实现了完整的用户角色管理体系: 1. 新增数据库表sys_user_role和sys_role_menu,完善权限关联结构 2. 新增角色状态字段,支持启用/禁用角色 3. 实现角色增删改查分页查询,新增角色列表接口 4. 实现用户分配角色、查询用户已分配角色的接口 5. 在前端用户管理页面添加角色分配功能,新增角色搜索筛选状态 6. 配置tauri命令自动收集,优化后端命令注册流程 7. 修复代码格式与依赖配置细节问题 --- rust.sql | 86 ++++++++++++++++++++---- src-tauri/Cargo.toml | 2 +- src-tauri/build.rs | 2 +- src-tauri/src/commands/menu.rs | 5 ++ src-tauri/src/commands/role.rs | 53 ++++++++++++--- src-tauri/src/commands/user.rs | 52 +++++++++++++++ src-tauri/src/lib.rs | 3 + src/api/system/role.ts | 67 ++++++++++--------- src/api/system/user.ts | 118 ++++++++++++++++++--------------- src/views/system/SysRole.vue | 27 ++++++-- src/views/system/SysUser.vue | 80 +++++++++++++++++++++- 11 files changed, 380 insertions(+), 115 deletions(-) diff --git a/rust.sql b/rust.sql index 2a4c082..9ee6fc0 100644 --- a/rust.sql +++ b/rust.sql @@ -1,9 +1,54 @@ -- Database export: rust --- Date: 2026-07-28 17:51:45 +-- Date: 2026-07-30 14:07:55 -- Generated by DBX SET FOREIGN_KEY_CHECKS = 0; +DROP TABLE IF EXISTS `sys_user_role`; + +CREATE TABLE `sys_user_role` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `creater` bigint(20) NOT NULL DEFAULT '0' COMMENT '创建人ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_role` (`user_id`,`role_id`), + KEY `idx_role_id` (`role_id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表'; + +INSERT INTO `sys_user_role` (`id`, `user_id`, `role_id`, `creater`, `create_time`) VALUES +(1, 1, 4, 0, '2026-07-30 06:03:51'), +(2, 1, 5, 0, '2026-07-30 06:03:51'), +(4, 2, 6, 0, '2026-07-30 06:04:40'); + +DROP TABLE IF EXISTS `sys_menu`; + +CREATE TABLE `sys_menu` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID', + `parent_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '父菜单ID(0为顶级菜单)', + `name` varchar(50) NOT NULL COMMENT '菜单名称(唯一标识,用于权限控制)', + `title` varchar(50) NOT NULL COMMENT '菜单标题(界面显示名称)', + `path` varchar(200) DEFAULT '' COMMENT '路由路径(Vue Router 路径)', + `component` varchar(200) DEFAULT '' COMMENT '组件路径(前端组件位置)', + `icon` varchar(100) DEFAULT '' COMMENT '菜单图标(如 el-icon-setting)', + `perms` varchar(200) DEFAULT '' COMMENT '权限标识(如 sys:user:list)', + `menu_type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '菜单类型(1=目录, 2=菜单, 3=按钮)', + `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序权重(数值越小越靠前)', + `visible` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否显示(0=隐藏, 1=显示)', + `status` bit(1) NOT NULL DEFAULT b'1' COMMENT '状态(0=禁用, 1=启用)', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_parent_id` (`parent_id`), + KEY `idx_name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COMMENT='系统菜单表'; + +INSERT INTO `sys_menu` (`id`, `parent_id`, `name`, `title`, `path`, `component`, `icon`, `perms`, `menu_type`, `sort`, `visible`, `status`, `create_time`, `update_time`) VALUES +(3, 0, 'Home', '首页', '/home', '/home', 'Home', '', 1, 10, b'1', b'1', '2026-07-29 03:54:25', '2026-07-29 03:54:25'), +(4, 0, 'System', '系统管理', '/system', '', 'Coin', '', 0, 20, b'1', b'1', '2026-07-29 03:55:43', '2026-07-29 03:55:43'), +(5, 4, 'SysUser', '用户管理', '/system/user', '/system/user', 'User', '', 1, 10, b'1', b'1', '2026-07-29 03:56:21', '2026-07-29 03:56:21'); + DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( @@ -24,23 +69,38 @@ CREATE TABLE `sys_user` ( ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表'; INSERT INTO `sys_user` (`id`, `nick_name`, `avatar`, `user_name`, `password`, `email`, `phone`, `age`, `sex`, `creater`, `create_time`) VALUES -(1, '管理员', '', 'admin', '123456', 'wu434425608@163.com', '18866668888', 18, 1, 1, '2026-07-27 14:37:35'), -(2, '用户', '', 'user', '123456', '122', '1', 0, 0, 0, '2026-07-27 07:20:45'); +(1, '管理员', 'https://q6.itc.cn/q_70/images03/20250306/355fba6a5cb049f5b98c2ed9f03cc5e1.jpeg', 'admin', '123456', 'wu434425608@163.com', '18866668888', 18, 1, 1, '2026-07-27 14:37:35'), +(2, '用户', 'https://q6.itc.cn/q_70/images03/20250306/355fba6a5cb049f5b98c2ed9f03cc5e1.jpeg', 'user', '123456', '122', '1', 0, 0, 0, '2026-07-27 07:20:45'); + +DROP TABLE IF EXISTS `sys_role_menu`; + +CREATE TABLE `sys_role_menu` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', + `creater` bigint(20) NOT NULL DEFAULT '0' COMMENT '创建人ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_role_menu` (`role_id`,`menu_id`), + KEY `idx_menu_id` (`menu_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色菜单关联表'; DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `role_key` varchar(50) DEFAULT '', - `role_name` varchar(50) DEFAULT '', - `sort` int(11) DEFAULT '0', - `creater` bigint(20) DEFAULT '0', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP, + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `role_key` varchar(50) DEFAULT '' COMMENT '编码', + `role_name` varchar(50) DEFAULT '' COMMENT '名称', + `sort` int(11) DEFAULT '0' COMMENT '排序', + `status` bit(1) DEFAULT b'1' COMMENT '状态', + `creater` bigint(20) DEFAULT '0' COMMENT '创建人', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='系统角色'; +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COMMENT='系统角色'; -INSERT INTO `sys_role` (`id`, `role_key`, `role_name`, `sort`, `creater`, `create_time`) VALUES -(1, 'admin', '管理员', 10, 0, '2026-07-28 09:25:19'), -(3, 'user', '用户', 30, 0, '2026-07-28 09:32:13'); +INSERT INTO `sys_role` (`id`, `role_key`, `role_name`, `sort`, `status`, `creater`, `create_time`) VALUES +(4, 'admin', '管理员', 20, b'1', 0, '2026-07-29 03:53:32'), +(5, 'superadmin', '超级管理员', 10, b'1', 0, '2026-07-29 03:53:46'), +(6, 'inventory', '仓管员', 30, b'1', 0, '2026-07-30 06:04:34'); SET FOREIGN_KEY_CHECKS = 1; diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3641771..cad12e3 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -35,7 +35,7 @@ sqlx = { version = "0.9.0", features = [ "chrono", ] } aws-sdk-s3 = "1.140.0" -aws-config = {version = "1.10.1", features = ["behavior-version-latest"] } +aws-config = { version = "1.10.1", features = ["behavior-version-latest"] } aws-credential-types = "1.3.0" aws-types = "1.5.0" tauri-helper = "0.2.1" diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 86fb009..aade1ae 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -1,4 +1,4 @@ fn main() { - // tauri_build::build() tauri_helper::generate_command_file(tauri_helper::TauriHelperOptions::default()); + tauri_build::build() } diff --git a/src-tauri/src/commands/menu.rs b/src-tauri/src/commands/menu.rs index eb6a7e0..d84eaed 100644 --- a/src-tauri/src/commands/menu.rs +++ b/src-tauri/src/commands/menu.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, MySqlPool, QueryBuilder}; use std::collections::HashMap; use tauri::State; +use tauri_helper::auto_collect_command; #[derive(Debug, Serialize, FromRow)] struct SysMenu { @@ -89,6 +90,7 @@ pub struct MenuSaveReq { } #[tauri::command] +#[auto_collect_command] pub async fn menu_list( pool: State<'_, MySqlPool>, params: MenuQueryReq, @@ -160,6 +162,7 @@ fn build_children(parent_id: i64, menu_map: &HashMap>) -> Vec } #[tauri::command] +#[auto_collect_command] pub async fn menu_save( pool: State<'_, MySqlPool>, params: MenuSaveReq, @@ -184,6 +187,7 @@ pub async fn menu_save( } #[tauri::command] +#[auto_collect_command] pub async fn menu_update( pool: State<'_, MySqlPool>, params: MenuSaveReq, @@ -208,6 +212,7 @@ pub async fn menu_update( } #[tauri::command] +#[auto_collect_command] pub async fn menu_del(pool: State<'_, MySqlPool>, id: i64) -> Result, String> { sqlx::query("DELETE FROM sys_menu WHERE id = ?") .bind(id) diff --git a/src-tauri/src/commands/role.rs b/src-tauri/src/commands/role.rs index 8ebf5b4..ddc6f8d 100644 --- a/src-tauri/src/commands/role.rs +++ b/src-tauri/src/commands/role.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use sqlx::{MySqlPool, QueryBuilder}; use tauri::State; +use tauri_helper::auto_collect_command; use crate::models::{ApiResponse, PageResult}; @@ -10,6 +11,7 @@ struct SysRole { role_key: String, role_name: String, sort: i32, + status: bool, creater: i64, create_time: chrono::NaiveDateTime, } @@ -20,6 +22,7 @@ pub struct RoleInfo { pub role_key: String, pub role_name: String, pub sort: i32, + pub status: bool, pub create_time: chrono::NaiveDateTime, } @@ -30,6 +33,7 @@ impl From for RoleInfo { role_key: role.role_key, role_name: role.role_name, sort: role.sort, + status: role.status, create_time: role.create_time, } } @@ -41,6 +45,7 @@ pub struct RolePageReq { pub page_size: i32, pub role_key: Option, pub role_name: Option, + pub status: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -49,9 +54,11 @@ pub struct RoleSaveReq { pub role_key: String, pub role_name: String, pub sort: i32, + pub status: bool, } #[tauri::command] +#[auto_collect_command] pub async fn role_save( pool: State<'_, MySqlPool>, params: RoleSaveReq, @@ -65,10 +72,11 @@ pub async fn role_save( if exist > 0 { return Ok(ApiResponse::error(2001, "角色_key已存在")); } - let result = sqlx::query("INSERT INTO sys_role (role_key, role_name, sort) VALUES (?, ?, ?)") + let result = sqlx::query("INSERT INTO sys_role (role_key, role_name, sort, status) VALUES (?, ?, ?, ?)") .bind(¶ms.role_key) .bind(¶ms.role_name) .bind(¶ms.sort) + .bind(¶ms.status) .execute(&*pool) .await .map_err(|e| e.to_string())?; @@ -77,6 +85,7 @@ pub async fn role_save( } #[tauri::command] +#[auto_collect_command] pub async fn role_page( pool: State<'_, MySqlPool>, params: RolePageReq, @@ -94,8 +103,9 @@ pub async fn role_page( .role_name .as_ref() .map_or(false, |role_name| !role_name.is_empty()); + let has_status = params.status.unwrap_or(false); - if has_role_key || has_role_name { + if has_role_key || has_role_name || has_status { count_builder.push(" WHERE 1=1"); if has_role_key { count_builder.push(" AND role_key LIKE "); @@ -105,6 +115,10 @@ pub async fn role_page( count_builder.push(" AND role_name LIKE "); count_builder.push_bind(format!("%{}%", params.role_name.as_deref().unwrap())); } + if has_status { + count_builder.push(" AND status = "); + count_builder.push_bind(¶ms.status); + } } let total: i64 = count_builder .build_query_scalar() @@ -113,7 +127,7 @@ pub async fn role_page( .map_err(|e| e.to_string())?; let mut query_builder = QueryBuilder::new("SELECT * FROM sys_role"); - if has_role_key || has_role_name { + if has_role_key || has_role_name || has_status { query_builder.push(" WHERE 1=1"); if has_role_key { query_builder.push(" AND role_key LIKE "); @@ -123,6 +137,10 @@ pub async fn role_page( query_builder.push(" AND role_name LIKE "); query_builder.push_bind(format!("%{}%", params.role_name.as_deref().unwrap())); } + if has_status { + query_builder.push(" AND status = "); + query_builder.push_bind(params.status); + } } query_builder.push(" ORDER BY sort ASC LIMIT "); query_builder.push_bind(page_size); @@ -147,24 +165,27 @@ pub async fn role_page( } #[tauri::command] +#[auto_collect_command] pub async fn role_update( pool: State<'_, MySqlPool>, params: RoleSaveReq, ) -> Result, String> { - let exist: i64 = sqlx::query_scalar("SELECT COUNT(id) FROM sys_role WHERE role_key = ? AND id != ?") - .bind(¶ms.role_key) - .bind(¶ms.id) - .fetch_one(&*pool) - .await - .map_err(|e| e.to_string())?; + let exist: i64 = + sqlx::query_scalar("SELECT COUNT(id) FROM sys_role WHERE role_key = ? AND id != ?") + .bind(¶ms.role_key) + .bind(¶ms.id) + .fetch_one(&*pool) + .await + .map_err(|e| e.to_string())?; if exist > 0 { return Ok(ApiResponse::error(2001, "角色_key已存在")); } - sqlx::query("UPDATE sys_role SET role_key = ?, role_name = ?, sort = ? WHERE id = ?") + sqlx::query("UPDATE sys_role SET role_key = ?, role_name = ?, sort = ?, status = ? WHERE id = ?") .bind(¶ms.role_key) .bind(¶ms.role_name) .bind(¶ms.sort) + .bind(¶ms.status) .bind(¶ms.id) .execute(&*pool) .await @@ -173,6 +194,7 @@ pub async fn role_update( } #[tauri::command] +#[auto_collect_command] pub async fn role_del(pool: State<'_, MySqlPool>, id: i64) -> Result, String> { sqlx::query("DELETE FROM sys_role WHERE id = ?") .bind(id) @@ -181,3 +203,14 @@ pub async fn role_del(pool: State<'_, MySqlPool>, id: i64) -> Result) -> Result>, String> { + let roles = sqlx::query_as::<_, SysRole>("SELECT * FROM sys_role WHERE status = 1") + .fetch_all(&*pool) + .await + .map_err(|e| e.to_string())?; + let role_list = roles.into_iter().map(|role| RoleInfo::from(role)).collect(); + Ok(ApiResponse::success(role_list)) +} diff --git a/src-tauri/src/commands/user.rs b/src-tauri/src/commands/user.rs index a0dda62..d336d37 100644 --- a/src-tauri/src/commands/user.rs +++ b/src-tauri/src/commands/user.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use sqlx::{MySqlPool, QueryBuilder}; use tauri::State; +use tauri_helper::auto_collect_command; use crate::models::{ApiResponse, PageResult}; @@ -67,7 +68,14 @@ impl From for UserInfo { } } +#[derive(Debug, Deserialize)] +pub struct UserAssignRole { + pub user_id: i64, + pub role_id: i64, +} + #[tauri::command] +#[auto_collect_command] pub async fn login( pool: State<'_, MySqlPool>, user_name: &str, @@ -92,6 +100,7 @@ pub async fn login( } #[tauri::command] +#[auto_collect_command] pub async fn get_user_page( pool: State<'_, MySqlPool>, params: UserPageReq, @@ -165,6 +174,7 @@ pub async fn get_user_page( } #[tauri::command] +#[auto_collect_command] pub async fn register( pool: State<'_, MySqlPool>, params: UserSaveReq, @@ -196,6 +206,7 @@ pub async fn register( } #[tauri::command] +#[auto_collect_command] pub async fn del_user(pool: State<'_, MySqlPool>, id: i64) -> Result, String> { sqlx::query("DELETE FROM sys_user WHERE id = ?") .bind(id) @@ -206,6 +217,7 @@ pub async fn del_user(pool: State<'_, MySqlPool>, id: i64) -> Result, params: UserSaveReq, @@ -235,6 +247,7 @@ pub async fn update_user( } #[tauri::command] +#[auto_collect_command] pub async fn get_user_by_id( pool: State<'_, MySqlPool>, id: i64, @@ -246,3 +259,42 @@ pub async fn get_user_by_id( .map_err(|e| e.to_string())?; Ok(ApiResponse::success(UserInfo::from(user))) } + +#[tauri::command] +#[auto_collect_command] +pub async fn assign_role( + pool: State<'_, MySqlPool>, + list: Vec, +) -> Result, String> { + let user_id = match list.first() { + Some(first) => first.user_id, + None => return Ok(ApiResponse::<()>::success_empty()), + }; + sqlx::query("DELETE FROM sys_user_role WHERE user_id = ?") + .bind(user_id) + .execute(&*pool) + .await + .map_err(|e| e.to_string())?; + + let mut query_builder = QueryBuilder::new("INSERT INTO sys_user_role (user_id, role_id)"); + query_builder.push_values(&list, |mut b, item| { + b.push_bind(item.user_id).push_bind(item.role_id); + }); + let _ = query_builder.build().execute(&*pool).await.map_err(|e| e.to_string())?; + Ok(ApiResponse::<()>::success_empty()) +} + +#[tauri::command] +#[auto_collect_command] +pub async fn get_user_role_ids( + pool: State<'_, MySqlPool>, + user_id: i64, +) -> Result>, String> { + let ids: Vec = + sqlx::query_scalar::<_, i64>("SELECT role_id FROM sys_user_role WHERE user_id = ?") + .bind(user_id) + .fetch_all(&*pool) + .await + .map_err(|e| e.to_string())?; + Ok(ApiResponse::success(ids)) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4abeb79..3a2dbd6 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3,6 +3,9 @@ pub mod commands; pub mod models; pub mod services; +use commands::menu::*; +use commands::role::*; +use commands::user::*; use services::db::create_db_pool; use services::s3::{S3Client, S3Config}; use tauri::Manager; diff --git a/src/api/system/role.ts b/src/api/system/role.ts index 3f9534f..7d55b9a 100644 --- a/src/api/system/role.ts +++ b/src/api/system/role.ts @@ -1,41 +1,48 @@ -import { invoke } from "@tauri-apps/api/core" -import { ApiResponse, PageResult } from "@/types" +import {invoke} from "@tauri-apps/api/core" +import {ApiResponse, PageResult} from "@/types" -export interface RoleInfo { - id: number - role_key: string - role_name: string - sort: number - create_time?: string +interface RoleInfo { + id: number + role_key: string + role_name: string + sort: number + status: boolean + create_time?: string } -export interface RolePageReq { - page: number - page_size: number - role_key?: string - role_name?: string +interface RolePageReq { + page: number + page_size: number + role_key?: string + role_name?: string + status?: boolean } -export interface RoleSaveReq { - id?: number - role_key: string - role_name: string - sort: number +interface RoleSaveReq { + id?: number + role_key: string + role_name: string + sort: number + status: boolean } const RoleApi = { - rolePage: async (params: RolePageReq): Promise>> => { - return await invoke("role_page", { params }) - }, - roleSave: async (params: RoleSaveReq): Promise> => { - return await invoke("role_save", { params }) - }, - roleUpdate: async (params: RoleSaveReq): Promise> => { - return await invoke("role_update", { params }) - }, - roleDel: async (id: number): Promise> => { - return await invoke("role_del", { id }) - }, + rolePage: async (params: RolePageReq): Promise>> => { + return await invoke("role_page", {params}) + }, + roleSave: async (params: RoleSaveReq): Promise> => { + return await invoke("role_save", {params}) + }, + roleUpdate: async (params: RoleSaveReq): Promise> => { + return await invoke("role_update", {params}) + }, + roleDel: async (id: number): Promise> => { + return await invoke("role_del", {id}) + }, + roleList: async (): Promise> => { + return await invoke("role_list") + } } export default RoleApi +export type {RoleInfo, RoleSaveReq, RolePageReq} \ No newline at end of file diff --git a/src/api/system/user.ts b/src/api/system/user.ts index 7c45f50..0cfc89f 100644 --- a/src/api/system/user.ts +++ b/src/api/system/user.ts @@ -1,63 +1,77 @@ -import { invoke } from "@tauri-apps/api/core" -import { ApiResponse, PageResult } from "@/types" +import {invoke} from "@tauri-apps/api/core" +import {ApiResponse, PageResult} from "@/types" -export interface UserInfo { - id: number - nick_name: string - avatar: string - user_name: string - email: string - phone: string - age: number - sex: number - create_time?: string +interface UserInfo { + id: number + nick_name: string + avatar: string + user_name: string + email: string + phone: string + age: number + sex: number + create_time?: string } -export interface UserPageReq { - page: number - page_size: number - nick_name?: string - user_name?: string +interface UserPageReq { + page: number + page_size: number + nick_name?: string + user_name?: string } -export interface UserSaveReq { - id?: number - nick_name: string - avatar: string - user_name: string - password: string - email: string - phone: string - age: number - sex: number - creater: number +interface UserSaveReq { + id?: number + nick_name: string + avatar: string + user_name: string + password: string + email: string + phone: string + age: number + sex: number + creater: number +} + +interface AssignRoleReq { + user_id: number + role_id: number } const UserApi = { - // 登录 - login: async (userName: string, password: string): Promise> => { - return await invoke("login", { userName, password }) - }, - // 获取用户分页列表 - getUserPage: async (params: UserPageReq): Promise>> => { - return await invoke("get_user_page", { params }) - }, - // 注册 - register: async (params: UserSaveReq): Promise> => { - return await invoke("register", { params }) - }, - // 删除用户 - delUser: async (id: number): Promise> => { - return await invoke("del_user", { id }) - }, - // 更新用户 - update_user: async (params: UserSaveReq): Promise> => { - return await invoke("update_user", { params }) - }, - // 根据ID获取用户信息 - getUserById: async (id: number): Promise> => { - return await invoke("get_user_by_id", { id }) - }, + // 登录 + login: async (userName: string, password: string): Promise> => { + return await invoke("login", {userName, password}) + }, + // 获取用户分页列表 + getUserPage: async (params: UserPageReq): Promise>> => { + return await invoke("get_user_page", {params}) + }, + // 注册 + register: async (params: UserSaveReq): Promise> => { + return await invoke("register", {params}) + }, + // 删除用户 + delUser: async (id: number): Promise> => { + return await invoke("del_user", {id}) + }, + // 更新用户 + update_user: async (params: UserSaveReq): Promise> => { + return await invoke("update_user", {params}) + }, + // 根据ID获取用户信息 + getUserById: async (id: number): Promise> => { + return await invoke("get_user_by_id", {id}) + }, + // 给用户分配角色 + assignRole: async (list: AssignRoleReq[]): Promise> => { + return await invoke("assign_role", {list}) + }, + // 获取用户角色id + getUserRole: async (userId: number): Promise> => { + return await invoke("get_user_role_ids", {userId}) + } } export default UserApi +export type {UserInfo, UserSaveReq, UserPageReq, AssignRoleReq} \ No newline at end of file diff --git a/src/views/system/SysRole.vue b/src/views/system/SysRole.vue index 21c1b89..6e04d44 100644 --- a/src/views/system/SysRole.vue +++ b/src/views/system/SysRole.vue @@ -12,6 +12,7 @@ + 查询 @@ -48,6 +49,12 @@ + + + + + + + + + +
+ + + + {{ role.role_name }} + {{ role.role_key }} + + + +
+ + +
@@ -138,8 +159,9 @@ import { ref, reactive, h } from 'vue' import { NButton, NIcon, useMessage, useDialog } from 'naive-ui' import type { DataTableColumns, FormInst, FormRules } from 'naive-ui' -import { SearchOutline, PersonAddOutline, CreateOutline, TrashOutline } from '@vicons/ionicons5' +import { SearchOutline, PersonAddOutline, CreateOutline, TrashOutline, ShieldCheckmarkOutline } from '@vicons/ionicons5' import UserApi, { type UserInfo, type UserSaveReq } from '@/api/system/user' +import RoleApi, { type RoleInfo } from '@/api/system/role' const message = useMessage() const dialog = useDialog() @@ -173,10 +195,14 @@ const columns: DataTableColumns = [ title: '创建时间', key: 'create_time', width: 170, render(row) { return row.create_time ?? '-' }, }, - { - title: '操作', key: 'actions', width: 150, align: 'center', + { + title: '操作', key: 'actions', width: 220, align: 'center', render(row) { return h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'center' } }, [ + h(NButton, { + text: true, size: 'tiny', type: 'info', + onClick: (e: Event) => { e.stopPropagation(); openRoleModal(row) }, + }, { icon: () => h(NIcon, null, { default: () => h(ShieldCheckmarkOutline) }), default: () => '角色' }), h(NButton, { text: true, size: 'tiny', type: 'primary', onClick: (e: Event) => { e.stopPropagation(); openEditModal(row) }, @@ -324,6 +350,54 @@ function handleDelete(row: UserInfo) { }, }) } + +// ─── 分配角色 ─── +const showRoleModal = ref(false) +const roleSubmitting = ref(false) +const allRoles = ref([]) +const checkedRoleIds = ref([]) +const selectedRoleUser = ref({ id: 0, nick_name: '', user_name: '', email: '', phone: '', avatar: '', age: 0, sex: 0 }) + +async function openRoleModal(row: UserInfo) { + selectedRoleUser.value = row + try { + const [rolesRes, roleIdsRes] = await Promise.all([ + RoleApi.roleList(), + UserApi.getUserRole(row.id), + ]) + if (rolesRes.success && rolesRes.data) { + allRoles.value = rolesRes.data + } + if (roleIdsRes.success && roleIdsRes.data) { + checkedRoleIds.value = roleIdsRes.data + } + } catch (e: any) { + message.error(e?.toString() ?? '加载角色信息失败') + return + } + showRoleModal.value = true +} + +async function handleRoleSubmit() { + roleSubmitting.value = true + try { + const list = checkedRoleIds.value.map(role_id => ({ + user_id: selectedRoleUser.value.id, + role_id, + })) + const res = await UserApi.assignRole(list) + if (res.success) { + message.success('分配角色成功') + showRoleModal.value = false + } else { + message.error(res.message) + } + } catch (e: any) { + message.error(e?.toString() ?? '分配角色失败') + } finally { + roleSubmitting.value = false + } +}