up
This commit is contained in:
@@ -2,8 +2,8 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"ops/models"
|
|
||||||
parsefmt "fmt"
|
parsefmt "fmt"
|
||||||
|
"ops/models"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -18,7 +18,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 更新管理员成员缓存
|
// 更新管理员成员缓存
|
||||||
func updatePurchaseAdminsCash() {
|
func PurchaseUpdateAdminsCash() {
|
||||||
purchaseAdmins = nil
|
purchaseAdmins = nil
|
||||||
// id 1 是系统管理员
|
// id 1 是系统管理员
|
||||||
purchaseAdmins = append(purchaseAdmins, 1)
|
purchaseAdmins = append(purchaseAdmins, 1)
|
||||||
@@ -92,8 +92,6 @@ type TabPurchaseCosts struct {
|
|||||||
CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime"`
|
CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// TabPurchaseCommit 记录订单状态变更及评论
|
// TabPurchaseCommit 记录订单状态变更及评论
|
||||||
type TabPurchaseCommit struct {
|
type TabPurchaseCommit struct {
|
||||||
ID uint `gorm:"primarykey"`
|
ID uint `gorm:"primarykey"`
|
||||||
@@ -132,7 +130,7 @@ func ApiPurchaseInit() {
|
|||||||
//先检查用户组有没有这个key purchase
|
//先检查用户组有没有这个key purchase
|
||||||
purchaseUserGroup.Name = "purchase_admin"
|
purchaseUserGroup.Name = "purchase_admin"
|
||||||
if models.DB.Where(&purchaseUserGroup).First(&purchaseUserGroup).Error == nil {
|
if models.DB.Where(&purchaseUserGroup).First(&purchaseUserGroup).Error == nil {
|
||||||
updatePurchaseAdminsCash()
|
PurchaseUpdateAdminsCash()
|
||||||
} else {
|
} else {
|
||||||
purchaseUserGroup.Type = "usergroup"
|
purchaseUserGroup.Type = "usergroup"
|
||||||
models.DB.Create(&purchaseUserGroup)
|
models.DB.Create(&purchaseUserGroup)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 更新管理员成员缓存
|
// 更新管理员成员缓存
|
||||||
func updateAdminsCash(){
|
func ScheduleUpdateAdminsCash() {
|
||||||
//先清空切片
|
//先清空切片
|
||||||
scheduleAdmins = nil
|
scheduleAdmins = nil
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ func ApiScheduleInit() {
|
|||||||
//先检查用户组有没有这个key
|
//先检查用户组有没有这个key
|
||||||
userGroup.Name = "schedule_admin"
|
userGroup.Name = "schedule_admin"
|
||||||
if models.DB.Where(&userGroup).First(&userGroup).Error == nil {
|
if models.DB.Where(&userGroup).First(&userGroup).Error == nil {
|
||||||
updateAdminsCash()
|
ScheduleUpdateAdminsCash()
|
||||||
} else {
|
} else {
|
||||||
userGroup.Type = "usergroup"
|
userGroup.Type = "usergroup"
|
||||||
models.DB.Create(&userGroup)
|
models.DB.Create(&userGroup)
|
||||||
@@ -249,7 +249,6 @@ func ApiSchedule(r *gin.RouterGroup) {
|
|||||||
models.DB.Create(&tosqllog)
|
models.DB.Create(&tosqllog)
|
||||||
ReturnJson(ctx, "apiOK", nil)
|
ReturnJson(ctx, "apiOK", nil)
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ReturnJson(ctx, "apiErr", nil)
|
ReturnJson(ctx, "apiErr", nil)
|
||||||
}
|
}
|
||||||
@@ -262,9 +261,6 @@ func ApiSchedule(r *gin.RouterGroup) {
|
|||||||
ReturnJson(ctx, "schedule_event_not_find", nil)
|
ReturnJson(ctx, "schedule_event_not_find", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ReturnJson(ctx, "jsonErr", nil)
|
ReturnJson(ctx, "jsonErr", nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,9 +157,9 @@ func ApiSysAdmin(r *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var params struct {
|
var params struct {
|
||||||
GroupID uint `json:"group_id"`
|
GroupID float64 `json:"group_id" mapstructure:"group_id"`
|
||||||
Page int `json:"page"`
|
Page float64 `json:"page" mapstructure:"page"`
|
||||||
PageSize int `json:"page_size"`
|
PageSize float64 `json:"page_size" mapstructure:"page_size"`
|
||||||
}
|
}
|
||||||
if err := mapstructure.Decode(data, ¶ms); err != nil {
|
if err := mapstructure.Decode(data, ¶ms); err != nil {
|
||||||
params.Page = 1
|
params.Page = 1
|
||||||
@@ -179,12 +179,21 @@ func ApiSysAdmin(r *gin.RouterGroup) {
|
|||||||
return
|
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 binds []TabUserGroupBinds
|
||||||
var total int64
|
var total int64
|
||||||
models.DB.Model(&TabUserGroupBinds{}).Where("group_id = ?", params.GroupID).Count(&total)
|
models.DB.Model(&TabUserGroupBinds{}).Where("group_id = ?", groupID).Count(&total)
|
||||||
models.DB.Where("group_id = ?", params.GroupID).Order("id ASC").Offset(offset).Limit(params.PageSize).Find(&binds)
|
models.DB.Where("group_id = ?", groupID).Order("id ASC").Offset(offset).Limit(pageSize).Find(&binds)
|
||||||
|
|
||||||
// 获取成员用户信息
|
// 获取成员用户信息
|
||||||
var members []map[string]interface{}
|
var members []map[string]interface{}
|
||||||
@@ -209,12 +218,12 @@ func ApiSysAdmin(r *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var redata map[string]interface{} = make(map[string]interface{})
|
var redata map[string]interface{} = make(map[string]interface{})
|
||||||
redata["group_id"] = params.GroupID
|
redata["group_id"] = groupID
|
||||||
redata["group_name"] = group.Name
|
redata["group_name"] = group.Name
|
||||||
redata["members"] = members
|
redata["members"] = members
|
||||||
redata["total"] = total
|
redata["total"] = total
|
||||||
redata["page"] = params.Page
|
redata["page"] = page
|
||||||
redata["page_size"] = params.PageSize
|
redata["page_size"] = pageSize
|
||||||
ReturnJson(ctx, "apiOK", redata)
|
ReturnJson(ctx, "apiOK", redata)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -316,6 +325,130 @@ func ApiSysAdmin(r *gin.RouterGroup) {
|
|||||||
ReturnJson(ctx, "apiOK", nil)
|
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) {
|
r.POST("/login_fail_logs", func(ctx *gin.Context) {
|
||||||
isAuth, _, data := AuthenticationAuthority(ctx)
|
isAuth, _, data := AuthenticationAuthority(ctx)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// updateWarehouseAdminsCash 刷新仓库管理员缓存
|
// updateWarehouseAdminsCash 刷新仓库管理员缓存
|
||||||
func updateWarehouseAdminsCash() {
|
func WarehouseUpdateAdminsCash() {
|
||||||
warehouseAdmins = nil
|
warehouseAdmins = nil
|
||||||
warehouseAdmins = append(warehouseAdmins, 1) // id=1 超级管理员
|
warehouseAdmins = append(warehouseAdmins, 1) // id=1 超级管理员
|
||||||
var binds []TabUserGroupBinds
|
var binds []TabUserGroupBinds
|
||||||
@@ -144,7 +144,7 @@ func ApiWarehouseInit() {
|
|||||||
|
|
||||||
warehouseUserGroup.Name = "warehouse_admin"
|
warehouseUserGroup.Name = "warehouse_admin"
|
||||||
if models.DB.Where(&warehouseUserGroup).First(&warehouseUserGroup).Error == nil {
|
if models.DB.Where(&warehouseUserGroup).First(&warehouseUserGroup).Error == nil {
|
||||||
updateWarehouseAdminsCash()
|
WarehouseUpdateAdminsCash()
|
||||||
} else {
|
} else {
|
||||||
warehouseUserGroup.Type = "usergroup"
|
warehouseUserGroup.Type = "usergroup"
|
||||||
models.DB.Create(&warehouseUserGroup)
|
models.DB.Create(&warehouseUserGroup)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// updateWorkOrderAdminsCash 刷新工单管理员缓存
|
// updateWorkOrderAdminsCash 刷新工单管理员缓存
|
||||||
func updateWorkOrderAdminsCash() {
|
func WorkOrderUpdateAdminsCash() {
|
||||||
workOrderAdmins = nil
|
workOrderAdmins = nil
|
||||||
workOrderAdmins = append(workOrderAdmins, 1) // id=1 超级管理员
|
workOrderAdmins = append(workOrderAdmins, 1) // id=1 超级管理员
|
||||||
var binds []TabUserGroupBinds
|
var binds []TabUserGroupBinds
|
||||||
@@ -50,8 +50,6 @@ type TabWorkOrder struct {
|
|||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type TabWorkOrderCommit struct {
|
type TabWorkOrderCommit struct {
|
||||||
ID uint `gorm:"primarykey"`
|
ID uint `gorm:"primarykey"`
|
||||||
WorkOrderID uint `gorm:"not null;index;comment:关联工单ID"`
|
WorkOrderID uint `gorm:"not null;index;comment:关联工单ID"`
|
||||||
@@ -76,8 +74,6 @@ type TabWorkOrderLog struct {
|
|||||||
CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime"`
|
CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// PurchaseOrderInfo 采购订单简要信息
|
// PurchaseOrderInfo 采购订单简要信息
|
||||||
type PurchaseOrderInfo struct {
|
type PurchaseOrderInfo struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
@@ -93,10 +89,9 @@ func ApiWorkOrderInit() {
|
|||||||
models.DB.AutoMigrate(&TabWorkOrderCommit{})
|
models.DB.AutoMigrate(&TabWorkOrderCommit{})
|
||||||
models.DB.AutoMigrate(&TabWorkOrderLog{})
|
models.DB.AutoMigrate(&TabWorkOrderLog{})
|
||||||
|
|
||||||
|
|
||||||
workOrderUserGroup.Name = "work_order_admin"
|
workOrderUserGroup.Name = "work_order_admin"
|
||||||
if models.DB.Where(&workOrderUserGroup).First(&workOrderUserGroup).Error == nil {
|
if models.DB.Where(&workOrderUserGroup).First(&workOrderUserGroup).Error == nil {
|
||||||
updateWorkOrderAdminsCash()
|
WorkOrderUpdateAdminsCash()
|
||||||
} else {
|
} else {
|
||||||
workOrderUserGroup.Type = "usergroup"
|
workOrderUserGroup.Type = "usergroup"
|
||||||
models.DB.Create(&workOrderUserGroup)
|
models.DB.Create(&workOrderUserGroup)
|
||||||
|
|||||||
@@ -36,6 +36,16 @@ export const authApi = {
|
|||||||
return api.post('/admin/group_members', { group_id: groupId, ...params })
|
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) {
|
getUserDetail(userId) {
|
||||||
return api.post('/admin/user_detail', { user_id: userId })
|
return api.post('/admin/user_detail', { user_id: userId })
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { useUserStore } from '@/stores/user'
|
|||||||
import { useUsersStore } from '@/stores/users'
|
import { useUsersStore } from '@/stores/users'
|
||||||
import { useToastStore } from '@/stores/toast'
|
import { useToastStore } from '@/stores/toast'
|
||||||
import { authApi } from '@/api/auth'
|
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()
|
const toast = useToastStore()
|
||||||
|
|
||||||
@@ -49,6 +50,17 @@ const userDetailLoading = ref(false)
|
|||||||
const newPassword = ref('')
|
const newPassword = ref('')
|
||||||
const resetPasswordLoading = ref(false)
|
const resetPasswordLoading = ref(false)
|
||||||
|
|
||||||
|
// 确认弹窗相关
|
||||||
|
const showConfirmDialog = ref(false)
|
||||||
|
const confirmDialogConfig = ref({
|
||||||
|
title: '确认',
|
||||||
|
message: '',
|
||||||
|
confirmText: '确认',
|
||||||
|
cancelText: '取消',
|
||||||
|
danger: false,
|
||||||
|
onConfirm: null,
|
||||||
|
})
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: 'users', label: '用户管理' },
|
{ id: 'users', label: '用户管理' },
|
||||||
{ id: 'groups', label: '用户组' },
|
{ id: 'groups', label: '用户组' },
|
||||||
@@ -106,6 +118,13 @@ function onPageChange(page) {
|
|||||||
|
|
||||||
const totalPages = computed(() => Math.ceil(userTotal.value / userPageSize.value))
|
const totalPages = computed(() => Math.ceil(userTotal.value / userPageSize.value))
|
||||||
const groupMemberTotalPages = computed(() => Math.ceil(groupMemberTotal.value / groupMemberPageSize.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))
|
const loginFailLogTotalPages = computed(() => Math.ceil(loginFailLogTotal.value / loginFailLogPageSize.value))
|
||||||
|
|
||||||
async function fetchGroups() {
|
async function fetchGroups() {
|
||||||
@@ -160,6 +179,97 @@ function onGroupMemberPageChange(page) {
|
|||||||
fetchGroupMembers()
|
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() {
|
async function fetchLoginFailLogs() {
|
||||||
loginFailLogsLoading.value = true
|
loginFailLogsLoading.value = true
|
||||||
try {
|
try {
|
||||||
@@ -482,21 +592,9 @@ onMounted(() => {
|
|||||||
: 'hover:bg-gray-50 dark:hover:bg-dk-base'
|
: 'hover:bg-gray-50 dark:hover:bg-dk-base'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center">
|
||||||
<div>
|
<div>
|
||||||
<div class="font-medium text-gray-900 dark:text-dk-text">{{ group.name }}</div>
|
<div class="font-medium text-gray-900 dark:text-dk-text">{{ group.name }}</div>
|
||||||
<div class="text-xs text-gray-500 dark:text-dk-subtle">
|
|
||||||
{{ group.memberCount }} 位成员
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex -space-x-1">
|
|
||||||
<img
|
|
||||||
v-for="memberId in group.memberIDs?.slice(0, 3)"
|
|
||||||
:key="memberId"
|
|
||||||
:src="usersStore.getAvatarUrlFromUserID(memberId)"
|
|
||||||
class="h-6 w-6 rounded-full border-2 border-white object-cover dark:border-dk-card"
|
|
||||||
:title="usersStore.getUsernameFromUserID(memberId)"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@@ -515,6 +613,12 @@ onMounted(() => {
|
|||||||
<h3 class="font-semibold text-gray-900 dark:text-dk-text">{{ selectedGroup.name }}</h3>
|
<h3 class="font-semibold text-gray-900 dark:text-dk-text">{{ selectedGroup.name }}</h3>
|
||||||
<p class="text-sm text-gray-500 dark:text-dk-subtle">共 {{ groupMemberTotal }} 位成员</p>
|
<p class="text-sm text-gray-500 dark:text-dk-subtle">共 {{ groupMemberTotal }} 位成员</p>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
@click="openAddMemberDialog"
|
||||||
|
class="flex items-center gap-1 rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
<IconPlus :size="16" /> 添加成员
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overflow-hidden rounded-md border border-gray-200 dark:border-dk-muted">
|
<div class="overflow-hidden rounded-md border border-gray-200 dark:border-dk-muted">
|
||||||
@@ -524,14 +628,15 @@ onMounted(() => {
|
|||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-dk-subtle">用户</th>
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-dk-subtle">用户</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-dk-subtle">邮箱</th>
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-dk-subtle">邮箱</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-dk-subtle">类型</th>
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-dk-subtle">类型</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-dk-subtle">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200 bg-white dark:divide-dk-muted dark:bg-dk-card">
|
<tbody class="divide-y divide-gray-200 bg-white dark:divide-dk-muted dark:bg-dk-card">
|
||||||
<tr v-if="groupMembersLoading" class="text-center">
|
<tr v-if="groupMembersLoading" class="text-center">
|
||||||
<td colspan="3" class="py-8 text-gray-500 dark:text-dk-subtle">加载中...</td>
|
<td colspan="4" class="py-8 text-gray-500 dark:text-dk-subtle">加载中...</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-else-if="groupMembers.length === 0" class="text-center">
|
<tr v-else-if="groupMembers.length === 0" class="text-center">
|
||||||
<td colspan="3" class="py-8 text-gray-500 dark:text-dk-subtle">暂无成员</td>
|
<td colspan="4" class="py-8 text-gray-500 dark:text-dk-subtle">暂无成员</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="member in groupMembers" :key="member.id" class="hover:bg-gray-50 dark:hover:bg-dk-base">
|
<tr v-for="member in groupMembers" :key="member.id" class="hover:bg-gray-50 dark:hover:bg-dk-base">
|
||||||
<td class="whitespace-nowrap px-4 py-3">
|
<td class="whitespace-nowrap px-4 py-3">
|
||||||
@@ -557,6 +662,14 @@ onMounted(() => {
|
|||||||
{{ member.type }}
|
{{ member.type }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
||||||
|
<button
|
||||||
|
@click="removeGroupMember(member.id)"
|
||||||
|
class="text-red-600 hover:text-red-700 dark:text-red-400"
|
||||||
|
>
|
||||||
|
移除
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -873,4 +986,104 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加成员弹窗 -->
|
||||||
|
<div
|
||||||
|
v-if="showAddMemberDialog"
|
||||||
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
||||||
|
@click.self="closeAddMemberDialog"
|
||||||
|
>
|
||||||
|
<div class="w-full max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-dk-card">
|
||||||
|
<div class="mb-4 flex items-center justify-between">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-dk-text">添加成员到 {{ selectedGroup?.name }}</h3>
|
||||||
|
<button
|
||||||
|
@click="closeAddMemberDialog"
|
||||||
|
class="text-gray-400 hover:text-gray-600 dark:text-dk-subtle dark:hover:text-dk-text"
|
||||||
|
>
|
||||||
|
<IconX :size="20" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索框 -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<input
|
||||||
|
v-model="addMemberSearch"
|
||||||
|
type="text"
|
||||||
|
placeholder="搜索用户名或邮箱..."
|
||||||
|
class="flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none dark:border-dk-muted dark:bg-dk-base dark:text-dk-text"
|
||||||
|
@keyup.enter="searchUsersToAdd"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
@click="searchUsersToAdd"
|
||||||
|
:disabled="addMemberSearchLoading"
|
||||||
|
class="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{{ addMemberSearchLoading ? '搜索中...' : '搜索' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索结果 -->
|
||||||
|
<div class="max-h-64 overflow-y-auto">
|
||||||
|
<div v-if="addMemberSearchLoading" class="py-4 text-center text-gray-500 dark:text-dk-subtle">
|
||||||
|
搜索中...
|
||||||
|
</div>
|
||||||
|
<div v-else-if="addMemberSearchResults.length === 0 && addMemberSearch" class="py-4 text-center text-gray-500 dark:text-dk-subtle">
|
||||||
|
未找到匹配的用户
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!addMemberSearch" class="py-4 text-center text-gray-500 dark:text-dk-subtle">
|
||||||
|
输入关键词搜索用户
|
||||||
|
</div>
|
||||||
|
<div v-else class="space-y-2">
|
||||||
|
<div
|
||||||
|
v-for="user in addMemberSearchResults"
|
||||||
|
:key="user.id"
|
||||||
|
class="flex items-center justify-between rounded-md border border-gray-200 p-3 dark:border-dk-muted"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<img
|
||||||
|
:src="usersStore.getAvatarUrlFromUserID(user.id)"
|
||||||
|
class="h-8 w-8 rounded-full object-cover"
|
||||||
|
alt="avatar"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div class="text-sm font-medium text-gray-900 dark:text-dk-text">
|
||||||
|
{{ usersStore.getUsernameFromUserID(user.id) || user.name }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 dark:text-dk-subtle">{{ user.email }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click="addGroupMember(user.id)"
|
||||||
|
:disabled="addMemberLoading"
|
||||||
|
class="rounded-md bg-green-600 px-3 py-1 text-xs font-medium text-white hover:bg-green-700 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
添加
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 flex justify-end">
|
||||||
|
<button
|
||||||
|
@click="closeAddMemberDialog"
|
||||||
|
class="rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-dk-muted dark:text-dk-text dark:hover:bg-dk-base"
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 确认弹窗 -->
|
||||||
|
<ConfirmDialog
|
||||||
|
v-model="showConfirmDialog"
|
||||||
|
:title="confirmDialogConfig.title"
|
||||||
|
:message="confirmDialogConfig.message"
|
||||||
|
:confirm-text="confirmDialogConfig.confirmText"
|
||||||
|
:cancel-text="confirmDialogConfig.cancelText"
|
||||||
|
:danger="confirmDialogConfig.danger"
|
||||||
|
@confirm="handleConfirm"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user