From 7db64658f98c99a776ffc33cd167de064c491844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=96=87=E5=B3=B0?= Date: Tue, 28 Apr 2026 20:25:20 +0800 Subject: [PATCH] up --- backend/my_work/routers/apiPurchase.go | 162 ++++++------ backend/my_work/routers/apiSchedule.go | 90 +++---- backend/my_work/routers/apiSysAdmin.go | 165 ++++++++++-- backend/my_work/routers/apiWarehouse.go | 4 +- backend/my_work/routers/apiWorkOrder.go | 13 +- frontend/ops_vue_js/src/api/auth.js | 10 + .../ops_vue_js/src/views/SysAdminView.vue | 245 ++++++++++++++++-- 7 files changed, 517 insertions(+), 172 deletions(-) diff --git a/backend/my_work/routers/apiPurchase.go b/backend/my_work/routers/apiPurchase.go index 24a44e7..73d497c 100644 --- a/backend/my_work/routers/apiPurchase.go +++ b/backend/my_work/routers/apiPurchase.go @@ -2,8 +2,8 @@ package routers import ( "encoding/json" - "ops/models" parsefmt "fmt" + "ops/models" "slices" "strings" "time" @@ -18,7 +18,7 @@ var ( ) // 更新管理员成员缓存 -func updatePurchaseAdminsCash() { +func PurchaseUpdateAdminsCash() { purchaseAdmins = nil // id 1 是系统管理员 purchaseAdmins = append(purchaseAdmins, 1) @@ -92,8 +92,6 @@ type TabPurchaseCosts struct { CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime"` } - - // TabPurchaseCommit 记录订单状态变更及评论 type TabPurchaseCommit struct { ID uint `gorm:"primarykey"` @@ -132,7 +130,7 @@ func ApiPurchaseInit() { //先检查用户组有没有这个key purchase purchaseUserGroup.Name = "purchase_admin" if models.DB.Where(&purchaseUserGroup).First(&purchaseUserGroup).Error == nil { - updatePurchaseAdminsCash() + PurchaseUpdateAdminsCash() } else { purchaseUserGroup.Type = "usergroup" models.DB.Create(&purchaseUserGroup) @@ -229,49 +227,49 @@ func ApiPurchase(r *gin.RouterGroup) { commitResps = append(commitResps, resp) } - // 查询关联工单(通过 TabWorkOrderPurchaseOrderBind 表) - type WorkOrderInfo struct { - ID uint `json:"id"` - Title string `json:"title"` - CurrentStatus string `json:"status"` - } - var linkedWorkOrders []WorkOrderInfo - var woBinds []TabWorkOrderPurchaseOrderBind - models.DB.Where("purchase_order_id = ?", from.ID).Find(&woBinds) - if len(woBinds) > 0 { - woIDSet := make(map[uint]bool) - var woIDs []uint - for _, wb := range woBinds { - if !woIDSet[wb.WorkOrderID] { - woIDSet[wb.WorkOrderID] = true - woIDs = append(woIDs, wb.WorkOrderID) + // 查询关联工单(通过 TabWorkOrderPurchaseOrderBind 表) + type WorkOrderInfo struct { + ID uint `json:"id"` + Title string `json:"title"` + CurrentStatus string `json:"status"` + } + var linkedWorkOrders []WorkOrderInfo + var woBinds []TabWorkOrderPurchaseOrderBind + models.DB.Where("purchase_order_id = ?", from.ID).Find(&woBinds) + if len(woBinds) > 0 { + woIDSet := make(map[uint]bool) + var woIDs []uint + for _, wb := range woBinds { + if !woIDSet[wb.WorkOrderID] { + woIDSet[wb.WorkOrderID] = true + woIDs = append(woIDs, wb.WorkOrderID) + } + } + var workOrders []TabWorkOrder + models.DB.Where("id IN ?", woIDs).Find(&workOrders) + for _, wo := range workOrders { + linkedWorkOrders = append(linkedWorkOrders, WorkOrderInfo{ + ID: wo.ID, + Title: wo.Title, + CurrentStatus: wo.CurrentStatus, + }) } } - var workOrders []TabWorkOrder - models.DB.Where("id IN ?", woIDs).Find(&workOrders) - for _, wo := range workOrders { - linkedWorkOrders = append(linkedWorkOrders, WorkOrderInfo{ - ID: wo.ID, - Title: wo.Title, - CurrentStatus: wo.CurrentStatus, - }) + if linkedWorkOrders == nil { + linkedWorkOrders = []WorkOrderInfo{} } - } - if linkedWorkOrders == nil { - linkedWorkOrders = []WorkOrderInfo{} - } - // 判断当前用户是否可以修改 - canModify := canModifyPurchase(user.ID, order.UserID) + // 判断当前用户是否可以修改 + canModify := canModifyPurchase(user.ID, order.UserID) - ReturnJson(ctx, "apiOK", gin.H{ - "order": order, - "canModify": canModify, - "costs": costs, - "photos": files, - "commits": commitResps, - "workOrders": linkedWorkOrders, - }) + ReturnJson(ctx, "apiOK", gin.H{ + "order": order, + "canModify": canModify, + "costs": costs, + "photos": files, + "commits": commitResps, + "workOrders": linkedWorkOrders, + }) }) // 更新订单状态(可附带评论) @@ -432,46 +430,46 @@ func ApiPurchase(r *gin.RouterGroup) { //fmt.Println(user) // DebugPrintJson(data) - type From_purchase_getorders struct { - Search string - Status string - Entries int - Page int + type From_purchase_getorders struct { + Search string + Status string + Entries int + Page int + } + + var jsondata From_purchase_getorders + if err := decodeJSON(data, &jsondata); err == nil { + //fmt.Println(jsondata) + + is_data_ok := true + + if jsondata.Entries <= 0 || jsondata.Entries > 300 { + is_data_ok = false + } + if jsondata.Page <= 0 { + is_data_ok = false } - var jsondata From_purchase_getorders - if err := decodeJSON(data, &jsondata); err == nil { - //fmt.Println(jsondata) + if is_data_ok { - is_data_ok := true - - if jsondata.Entries <= 0 || jsondata.Entries > 300 { - is_data_ok = false - } - if jsondata.Page <= 0 { - is_data_ok = false - } - - if is_data_ok { - - //读取有多少条目 - var count int64 - query := models.DB.Model(TabPurchaseOrder{}) - if jsondata.Search != "" { - // 精确匹配订单 ID - var id uint - if _, err := parsefmt.Sscanf(jsondata.Search, "%d", &id); err == nil && id > 0 { - query = query.Where("id = ?", id) - } else { - // 模糊匹配标题和用途(Remark) - query = query.Where("title LIKE ? OR remark LIKE ?", - "%"+jsondata.Search+"%", "%"+jsondata.Search+"%") - } + //读取有多少条目 + var count int64 + query := models.DB.Model(TabPurchaseOrder{}) + if jsondata.Search != "" { + // 精确匹配订单 ID + var id uint + if _, err := parsefmt.Sscanf(jsondata.Search, "%d", &id); err == nil && id > 0 { + query = query.Where("id = ?", id) + } else { + // 模糊匹配标题和用途(Remark) + query = query.Where("title LIKE ? OR remark LIKE ?", + "%"+jsondata.Search+"%", "%"+jsondata.Search+"%") } - if jsondata.Status != "" { - query = query.Where("order_status = ?", jsondata.Status) - } - query.Count(&count) + } + if jsondata.Status != "" { + query = query.Where("order_status = ?", jsondata.Status) + } + query.Count(&count) //读取条目 var getorders []TabPurchaseOrder @@ -820,11 +818,11 @@ func ApiPurchase(r *gin.RouterGroup) { ReturnJson(ctx, "apiOK", map[string]interface{}{ "pending": count.Pending, "ordered": count.Ordered, - "arrived": count.Arrived, + "arrived": count.Arrived, "received": count.Received, - "lost": count.Lost, + "lost": count.Lost, "returned": count.Returned, - "total": count.Total, + "total": count.Total, }) }) diff --git a/backend/my_work/routers/apiSchedule.go b/backend/my_work/routers/apiSchedule.go index ea1c765..2fbb7f4 100644 --- a/backend/my_work/routers/apiSchedule.go +++ b/backend/my_work/routers/apiSchedule.go @@ -40,7 +40,7 @@ type TabScheduleLog struct { CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime;comment:操作时间"` } type fromAddEvent struct { - ID uint `json:"id"` + ID uint `json:"id"` Title string `json:"title" binding:"required"` // 日程标题 Start string `json:"start" binding:"required"` // 开始日期 End string `json:"end" binding:"required"` // 结束日期 @@ -53,27 +53,27 @@ type fromGetEvents struct { } var ( - userGroup TabUserGroups + userGroup TabUserGroups scheduleAdmins []uint ) -//更新管理员成员缓存 -func updateAdminsCash(){ -//先清空切片 -scheduleAdmins=nil +// 更新管理员成员缓存 +func ScheduleUpdateAdminsCash() { + //先清空切片 + scheduleAdmins = nil -//获取管理员用户组id -//id 1是系统管理员 直接appen - scheduleAdmins=append(scheduleAdmins, 1) + //获取管理员用户组id + //id 1是系统管理员 直接appen + scheduleAdmins = append(scheduleAdmins, 1) //读取所有绑定了这个用户组的用户id var usergroupbind []TabUserGroupBinds - usergroupbindfind:= TabUserGroupBinds{ + usergroupbindfind := TabUserGroupBinds{ GroupID: userGroup.ID, } models.DB.Where(&usergroupbindfind).Find(&usergroupbind) - for _ , item:= range usergroupbind{ - if !slices.Contains(scheduleAdmins,item.UserID){ - scheduleAdmins=append(scheduleAdmins, item.UserID) + for _, item := range usergroupbind { + if !slices.Contains(scheduleAdmins, item.UserID) { + scheduleAdmins = append(scheduleAdmins, item.UserID) } } @@ -83,11 +83,11 @@ func ApiScheduleInit() { //先初始化数据表 models.DB.AutoMigrate(&TabSchedule{}) models.DB.AutoMigrate(&TabScheduleLog{}) - + //先检查用户组有没有这个key userGroup.Name = "schedule_admin" if models.DB.Where(&userGroup).First(&userGroup).Error == nil { - updateAdminsCash() + ScheduleUpdateAdminsCash() } else { userGroup.Type = "usergroup" models.DB.Create(&userGroup) @@ -115,7 +115,7 @@ func ApiSchedule(r *gin.RouterGroup) { //已登录 进一步判断编辑权限 temp["edit"] = false - if slices.Contains(scheduleAdmins,user.ID){ + if slices.Contains(scheduleAdmins, user.ID) { temp["edit"] = true } if item.UserID == user.ID { @@ -149,29 +149,29 @@ func ApiSchedule(r *gin.RouterGroup) { var from fromAddEvent if err := mapstructure.Decode(data, &from); err == nil { //先从数据库拉取原始event数据 - oldEvent:=TabSchedule{} - if models.DB.Where("id = ?", from.ID).First(&oldEvent).Error==nil{ + oldEvent := TabSchedule{} + if models.DB.Where("id = ?", from.ID).First(&oldEvent).Error == nil { //需要先判断修改权限 - var isCanEdit=false - if slices.Contains(scheduleAdmins,user.ID){ //用户id是管理员 + var isCanEdit = false + if slices.Contains(scheduleAdmins, user.ID) { //用户id是管理员 isCanEdit = true } - if oldEvent.UserID==user.ID{//event是用户创建的 + if oldEvent.UserID == user.ID { //event是用户创建的 isCanEdit = true } - if isCanEdit{ + if isCanEdit { tosql := TabSchedule{} tosql.DeletedAt.Scan(time.Now()) //fmt.Println(tosql) - findEvent:=TabSchedule{ + findEvent := TabSchedule{ ID: oldEvent.ID, } - if models.DB.Where(&findEvent).Updates(&tosql).Error==nil{ + if models.DB.Where(&findEvent).Updates(&tosql).Error == nil { //应该修改完了 写日志 //把最新数据再读出来 models.DB.Where(&findEvent).First(&findEvent) newContent, _ := json.Marshal(findEvent) //转 JSON - oldContent, _ := json.Marshal(oldEvent) //转 JSON + oldContent, _ := json.Marshal(oldEvent) //转 JSON tosqllog := TabScheduleLog{ UserID: user.ID, ScheduleID: oldEvent.ID, @@ -182,13 +182,13 @@ func ApiSchedule(r *gin.RouterGroup) { } models.DB.Create(&tosqllog) ReturnJson(ctx, "apiOK", nil) - }else{ + } else { ReturnJson(ctx, "apiErr", nil) } - }else{ + } else { ReturnJson(ctx, "schedule_permission_denied", nil) } - }else{ + } else { ReturnJson(ctx, "schedule_event_not_find", nil) } } else { @@ -201,24 +201,24 @@ func ApiSchedule(r *gin.RouterGroup) { }) r.POST("/editevent", func(ctx *gin.Context) { - isAuth, user, data := AuthenticationAuthority(ctx) + isAuth, user, data := AuthenticationAuthority(ctx) if isAuth { - + var from fromAddEvent if err := mapstructure.Decode(data, &from); err == nil { //先从数据库拉取原始event数据 - oldEvent:=TabSchedule{} - if models.DB.Where("id = ?", from.ID).First(&oldEvent).Error==nil{ + oldEvent := TabSchedule{} + if models.DB.Where("id = ?", from.ID).First(&oldEvent).Error == nil { //需要先判断修改权限 - var isCanEdit=false - if slices.Contains(scheduleAdmins,user.ID){ //用户id是管理员 + var isCanEdit = false + if slices.Contains(scheduleAdmins, user.ID) { //用户id是管理员 isCanEdit = true } - if oldEvent.UserID==user.ID{//event是用户创建的 + if oldEvent.UserID == user.ID { //event是用户创建的 isCanEdit = true } - if isCanEdit{ + if isCanEdit { tosql := TabSchedule{ // UserID: user.ID, //如果是管理员修改的话会覆盖掉创建者的id Title: from.Title, @@ -228,16 +228,16 @@ func ApiSchedule(r *gin.RouterGroup) { } //fmt.Println(tosql) - findEvent:=TabSchedule{ + findEvent := TabSchedule{ ID: oldEvent.ID, } - if models.DB.Where(&findEvent).Updates(&tosql).Error==nil{ + if models.DB.Where(&findEvent).Updates(&tosql).Error == nil { //应该修改完了 写日志 //把最新数据再读出来 models.DB.Where(&findEvent).First(&findEvent) newContent, _ := json.Marshal(findEvent) //转 JSON - oldContent, _ := json.Marshal(oldEvent) //转 JSON + oldContent, _ := json.Marshal(oldEvent) //转 JSON tosqllog := TabScheduleLog{ UserID: user.ID, ScheduleID: oldEvent.ID, @@ -249,22 +249,18 @@ func ApiSchedule(r *gin.RouterGroup) { models.DB.Create(&tosqllog) ReturnJson(ctx, "apiOK", nil) - - }else{ + } else { ReturnJson(ctx, "apiErr", nil) } - - }else{ + + } else { ReturnJson(ctx, "schedule_permission_denied", nil) } - }else{ + } else { ReturnJson(ctx, "schedule_event_not_find", nil) } - - - } else { ReturnJson(ctx, "jsonErr", nil) } diff --git a/backend/my_work/routers/apiSysAdmin.go b/backend/my_work/routers/apiSysAdmin.go index 757da2b..af25214 100644 --- a/backend/my_work/routers/apiSysAdmin.go +++ b/backend/my_work/routers/apiSysAdmin.go @@ -156,15 +156,15 @@ func ApiSysAdmin(r *gin.RouterGroup) { return } - var params struct { - GroupID uint `json:"group_id"` - Page int `json:"page"` - PageSize int `json:"page_size"` - } - if err := mapstructure.Decode(data, ¶ms); err != nil { - params.Page = 1 - params.PageSize = 20 - } + var params struct { + GroupID float64 `json:"group_id" mapstructure:"group_id"` + Page float64 `json:"page" mapstructure:"page"` + PageSize float64 `json:"page_size" mapstructure:"page_size"` + } + if err := mapstructure.Decode(data, ¶ms); err != nil { + params.Page = 1 + params.PageSize = 20 + } if params.Page < 1 { params.Page = 1 } @@ -179,12 +179,21 @@ func ApiSysAdmin(r *gin.RouterGroup) { return } - offset := (params.Page - 1) * params.PageSize + groupID := uint(params.GroupID) + page := int(params.Page) + pageSize := int(params.PageSize) + if page < 1 { + page = 1 + } + if pageSize < 1 || pageSize > 100 { + pageSize = 20 + } + offset := (page - 1) * pageSize var binds []TabUserGroupBinds var total int64 - models.DB.Model(&TabUserGroupBinds{}).Where("group_id = ?", params.GroupID).Count(&total) - models.DB.Where("group_id = ?", params.GroupID).Order("id ASC").Offset(offset).Limit(params.PageSize).Find(&binds) + models.DB.Model(&TabUserGroupBinds{}).Where("group_id = ?", groupID).Count(&total) + models.DB.Where("group_id = ?", groupID).Order("id ASC").Offset(offset).Limit(pageSize).Find(&binds) // 获取成员用户信息 var members []map[string]interface{} @@ -209,12 +218,12 @@ func ApiSysAdmin(r *gin.RouterGroup) { } var redata map[string]interface{} = make(map[string]interface{}) - redata["group_id"] = params.GroupID + redata["group_id"] = groupID redata["group_name"] = group.Name redata["members"] = members redata["total"] = total - redata["page"] = params.Page - redata["page_size"] = params.PageSize + redata["page"] = page + redata["page_size"] = pageSize ReturnJson(ctx, "apiOK", redata) }) @@ -316,7 +325,131 @@ func ApiSysAdmin(r *gin.RouterGroup) { ReturnJson(ctx, "apiOK", nil) }) - // 获取登录失败日志(仅系统管理员可访问) + // 添加用户组成员(仅系统管理员可访问) + r.POST("/add_group_member", func(ctx *gin.Context) { + isAuth, adminUser, data := AuthenticationAuthority(ctx) + if !isAuth { + ReturnJson(ctx, "userNoLogin", nil) + return + } + + // 检查是否为系统管理员 + if !SysAdminCheck(adminUser.ID) { + ReturnJson(ctx, "permission_denied", nil) + return + } + + var params struct { + GroupID float64 `json:"group_id" mapstructure:"group_id"` + UserID float64 `json:"user_id" mapstructure:"user_id"` + } + if err := mapstructure.Decode(data, ¶ms); err != nil || params.GroupID == 0 || params.UserID == 0 { + ReturnJson(ctx, "parameErr", nil) + return + } + + // 验证用户组是否存在 + var group TabUserGroups + if models.DB.First(&group, uint(params.GroupID)).Error != nil { + ReturnJson(ctx, "groupNotFound", nil) + return + } + + // 验证用户是否存在 + var user TabUser + if models.DB.First(&user, uint(params.UserID)).Error != nil { + ReturnJson(ctx, "userNotFound", nil) + return + } + + // 检查绑定是否已存在 + var existingBind TabUserGroupBinds + if models.DB.Where("group_id = ? AND user_id = ?", uint(params.GroupID), uint(params.UserID)).First(&existingBind).Error == nil { + ReturnJson(ctx, "userAlreadyInGroup", nil) + return + } + + // 创建绑定 + newBind := TabUserGroupBinds{ + UserID: uint(params.UserID), + GroupID: uint(params.GroupID), + } + if err := models.DB.Create(&newBind).Error; err != nil { + ReturnJson(ctx, "dbErr", nil) + return + } + + // 根据组名刷新对应的权限缓存 + switch group.Name { + case "admins": + updateSysAdminsCash() + case "schedule_admin": + ScheduleUpdateAdminsCash() + case "purchase_admin": + PurchaseUpdateAdminsCash() + case "work_order_admin": + WorkOrderUpdateAdminsCash() + case "warehouse_admin": + WarehouseUpdateAdminsCash() + } + + ReturnJson(ctx, "apiOK", nil) +}) + +// 移除用户组成员(仅系统管理员可访问) + r.POST("/remove_group_member", func(ctx *gin.Context) { + isAuth, adminUser, data := AuthenticationAuthority(ctx) + if !isAuth { + ReturnJson(ctx, "userNoLogin", nil) + return + } + + // 检查是否为系统管理员 + if !SysAdminCheck(adminUser.ID) { + ReturnJson(ctx, "permission_denied", nil) + return + } + + var params struct { + GroupID float64 `json:"group_id" mapstructure:"group_id"` + UserID float64 `json:"user_id" mapstructure:"user_id"` + } + if err := mapstructure.Decode(data, ¶ms); err != nil || params.GroupID == 0 || params.UserID == 0 { + ReturnJson(ctx, "parameErr", nil) + return + } + + // 验证用户组是否存在 + var group TabUserGroups + if models.DB.First(&group, uint(params.GroupID)).Error != nil { + ReturnJson(ctx, "groupNotFound", nil) + return + } + + // 删除绑定 + if err := models.DB.Where("group_id = ? AND user_id = ?", uint(params.GroupID), uint(params.UserID)).Delete(&TabUserGroupBinds{}).Error; err != nil { + ReturnJson(ctx, "dbErr", nil) + return + } + + // 根据组名刷新对应的权限缓存 + switch group.Name { + case "admins": + updateSysAdminsCash() + case "schedule_admin": + ScheduleUpdateAdminsCash() + case "purchase_admin": + PurchaseUpdateAdminsCash() + case "work_order_admin": + WorkOrderUpdateAdminsCash() + case "warehouse_admin": + WarehouseUpdateAdminsCash() + } + + ReturnJson(ctx, "apiOK", nil) +}) + +// 获取登录失败日志(仅系统管理员可访问) r.POST("/login_fail_logs", func(ctx *gin.Context) { isAuth, _, data := AuthenticationAuthority(ctx) if !isAuth { diff --git a/backend/my_work/routers/apiWarehouse.go b/backend/my_work/routers/apiWarehouse.go index daf04a5..e661e96 100644 --- a/backend/my_work/routers/apiWarehouse.go +++ b/backend/my_work/routers/apiWarehouse.go @@ -71,7 +71,7 @@ var ( ) // updateWarehouseAdminsCash 刷新仓库管理员缓存 -func updateWarehouseAdminsCash() { +func WarehouseUpdateAdminsCash() { warehouseAdmins = nil warehouseAdmins = append(warehouseAdmins, 1) // id=1 超级管理员 var binds []TabUserGroupBinds @@ -144,7 +144,7 @@ func ApiWarehouseInit() { warehouseUserGroup.Name = "warehouse_admin" if models.DB.Where(&warehouseUserGroup).First(&warehouseUserGroup).Error == nil { - updateWarehouseAdminsCash() + WarehouseUpdateAdminsCash() } else { warehouseUserGroup.Type = "usergroup" models.DB.Create(&warehouseUserGroup) diff --git a/backend/my_work/routers/apiWorkOrder.go b/backend/my_work/routers/apiWorkOrder.go index f9bc455..6eec00f 100644 --- a/backend/my_work/routers/apiWorkOrder.go +++ b/backend/my_work/routers/apiWorkOrder.go @@ -17,7 +17,7 @@ var ( ) // updateWorkOrderAdminsCash 刷新工单管理员缓存 -func updateWorkOrderAdminsCash() { +func WorkOrderUpdateAdminsCash() { workOrderAdmins = nil workOrderAdmins = append(workOrderAdmins, 1) // id=1 超级管理员 var binds []TabUserGroupBinds @@ -50,8 +50,6 @@ type TabWorkOrder struct { DeletedAt gorm.DeletedAt `gorm:"index"` } - - type TabWorkOrderCommit struct { ID uint `gorm:"primarykey"` WorkOrderID uint `gorm:"not null;index;comment:关联工单ID"` @@ -76,8 +74,6 @@ type TabWorkOrderLog struct { CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime"` } - - // PurchaseOrderInfo 采购订单简要信息 type PurchaseOrderInfo struct { ID uint `json:"id"` @@ -89,14 +85,13 @@ type PurchaseOrderInfo struct { func ApiWorkOrderInit() { models.DB.AutoMigrate(&TabWorkOrder{}) - + models.DB.AutoMigrate(&TabWorkOrderCommit{}) models.DB.AutoMigrate(&TabWorkOrderLog{}) - workOrderUserGroup.Name = "work_order_admin" if models.DB.Where(&workOrderUserGroup).First(&workOrderUserGroup).Error == nil { - updateWorkOrderAdminsCash() + WorkOrderUpdateAdminsCash() } else { workOrderUserGroup.Type = "usergroup" models.DB.Create(&workOrderUserGroup) @@ -348,7 +343,7 @@ func ApiWorkOrder(r *gin.RouterGroup) { // 为每条 commit 附加图片和采购订单 type CommitWithPhotos struct { TabWorkOrderCommit - Photos []TabFileInfo `json:"photos"` + Photos []TabFileInfo `json:"photos"` PurchaseOrders []PurchaseOrderInfo `json:"purchaseOrders"` } var commitsWithPhotos []CommitWithPhotos diff --git a/frontend/ops_vue_js/src/api/auth.js b/frontend/ops_vue_js/src/api/auth.js index f657fee..031c139 100644 --- a/frontend/ops_vue_js/src/api/auth.js +++ b/frontend/ops_vue_js/src/api/auth.js @@ -36,6 +36,16 @@ export const authApi = { return api.post('/admin/group_members', { group_id: groupId, ...params }) }, + /** 添加用户组成员(仅管理员可访问) */ + addGroupMember(groupId, userId) { + return api.post('/admin/add_group_member', { group_id: groupId, user_id: userId }) + }, + + /** 移除用户组成员(仅管理员可访问) */ + removeGroupMember(groupId, userId) { + return api.post('/admin/remove_group_member', { group_id: groupId, user_id: userId }) + }, + /** 获取用户详细信息(仅管理员可访问) */ getUserDetail(userId) { return api.post('/admin/user_detail', { user_id: userId }) diff --git a/frontend/ops_vue_js/src/views/SysAdminView.vue b/frontend/ops_vue_js/src/views/SysAdminView.vue index 2fca75b..a1e0710 100644 --- a/frontend/ops_vue_js/src/views/SysAdminView.vue +++ b/frontend/ops_vue_js/src/views/SysAdminView.vue @@ -4,7 +4,8 @@ import { useUserStore } from '@/stores/user' import { useUsersStore } from '@/stores/users' import { useToastStore } from '@/stores/toast' import { authApi } from '@/api/auth' -import { IconSearch, IconRefresh, IconChevronLeft, IconChevronRight } from '@tabler/icons-vue' +import ConfirmDialog from '@/components/ConfirmDialog.vue' +import { IconSearch, IconRefresh, IconChevronLeft, IconChevronRight, IconPlus, IconX } from '@tabler/icons-vue' const toast = useToastStore() @@ -49,6 +50,17 @@ const userDetailLoading = ref(false) const newPassword = ref('') const resetPasswordLoading = ref(false) +// 确认弹窗相关 +const showConfirmDialog = ref(false) +const confirmDialogConfig = ref({ + title: '确认', + message: '', + confirmText: '确认', + cancelText: '取消', + danger: false, + onConfirm: null, +}) + const tabs = [ { id: 'users', label: '用户管理' }, { id: 'groups', label: '用户组' }, @@ -106,6 +118,13 @@ function onPageChange(page) { const totalPages = computed(() => Math.ceil(userTotal.value / userPageSize.value)) const groupMemberTotalPages = computed(() => Math.ceil(groupMemberTotal.value / groupMemberPageSize.value)) + +// 添加成员弹窗相关 +const showAddMemberDialog = ref(false) +const addMemberSearch = ref('') +const addMemberSearchResults = ref([]) +const addMemberLoading = ref(false) +const addMemberSearchLoading = ref(false) const loginFailLogTotalPages = computed(() => Math.ceil(loginFailLogTotal.value / loginFailLogPageSize.value)) async function fetchGroups() { @@ -160,6 +179,97 @@ function onGroupMemberPageChange(page) { fetchGroupMembers() } +async function openAddMemberDialog() { + showAddMemberDialog.value = true + addMemberSearch.value = '' + addMemberSearchResults.value = [] +} + +function closeAddMemberDialog() { + showAddMemberDialog.value = false + addMemberSearch.value = '' + addMemberSearchResults.value = [] +} + +async function searchUsersToAdd() { + if (!addMemberSearch.value.trim()) { + addMemberSearchResults.value = [] + return + } + addMemberSearchLoading.value = true + try { + const res = await authApi.getUsers({ + page: 1, + page_size: 10, + search: addMemberSearch.value, + }) + if (res.errCode === 0) { + // 过滤掉已经在组中的用户 + const existingMemberIds = new Set(groupMembers.value.map(m => m.id)) + addMemberSearchResults.value = (res.data.users || []).filter(u => !existingMemberIds.has(u.id)) + } + } catch { + // 错误已由拦截器处理 + } finally { + addMemberSearchLoading.value = false + } +} + +async function addGroupMember(userId) { + if (!selectedGroup.value) return + addMemberLoading.value = true + try { + const res = await authApi.addGroupMember(selectedGroup.value.id, userId) + if (res.errCode === 0) { + toast.success('成员添加成功') + fetchGroupMembers() + // 从搜索结果中移除已添加的用户 + addMemberSearchResults.value = addMemberSearchResults.value.filter(u => u.id !== userId) + } else { + toast.error(res.raw?.err_msg || '添加失败') + } + } catch { + // 错误已由拦截器处理 + } finally { + addMemberLoading.value = false + } +} + +function openConfirmDialog(config) { + confirmDialogConfig.value = { ...confirmDialogConfig.value, ...config } + showConfirmDialog.value = true +} + +function handleConfirm() { + if (confirmDialogConfig.value.onConfirm) { + confirmDialogConfig.value.onConfirm() + } + showConfirmDialog.value = false +} + +async function removeGroupMember(userId) { + if (!selectedGroup.value) return + openConfirmDialog({ + title: '移除成员', + message: '确定要移除该成员吗?', + confirmText: '移除', + danger: true, + onConfirm: async () => { + try { + const res = await authApi.removeGroupMember(selectedGroup.value.id, userId) + if (res.errCode === 0) { + toast.success('成员移除成功') + fetchGroupMembers() + } else { + toast.error(res.raw?.err_msg || '移除失败') + } + } catch { + // 错误已由拦截器处理 + } + }, + }) +} + async function fetchLoginFailLogs() { loginFailLogsLoading.value = true try { @@ -482,21 +592,9 @@ onMounted(() => { : 'hover:bg-gray-50 dark:hover:bg-dk-base' ]" > -
+
{{ group.name }}
-
- {{ group.memberCount }} 位成员 -
-
-
-
@@ -515,6 +613,12 @@ onMounted(() => {

{{ selectedGroup.name }}

共 {{ groupMemberTotal }} 位成员

+
@@ -524,14 +628,15 @@ onMounted(() => { 用户 邮箱 类型 + 操作 - 加载中... + 加载中... - 暂无成员 + 暂无成员 @@ -557,6 +662,14 @@ onMounted(() => { {{ member.type }} + + + @@ -873,4 +986,104 @@ onMounted(() => {
+ + +
+
+
+

添加成员到 {{ selectedGroup?.name }}

+ +
+ + +
+
+ + +
+
+ + +
+
+ 搜索中... +
+
+ 未找到匹配的用户 +
+
+ 输入关键词搜索用户 +
+
+
+
+ avatar +
+
+ {{ usersStore.getUsernameFromUserID(user.id) || user.name }} +
+
{{ user.email }}
+
+
+ +
+
+
+ +
+ +
+
+
+ + +