diff --git a/server/api/admin/iminvite/im_invite.go b/server/api/admin/iminvite/im_invite.go index 13116c5..45ebe03 100644 --- a/server/api/admin/iminvite/im_invite.go +++ b/server/api/admin/iminvite/im_invite.go @@ -51,6 +51,13 @@ type RevokeReq struct { type RevokeRes struct{} +type BindUserReq struct { + g.Meta `path:"/imInvite/bindUser" method:"post" tags:"IM邀请码" summary:"绑定邀请码用户"` + sysin.ImInviteBindUserInp +} + +type BindUserRes struct{} + type BatchRevokeReq struct { g.Meta `path:"/imInvite/batchRevoke" method:"post" tags:"IM邀请码" summary:"批量撤销IM注册邀请码"` sysin.ImInviteBatchRevokeInp diff --git a/server/internal/controller/admin/sys/im_invite.go b/server/internal/controller/admin/sys/im_invite.go index f9761dc..3968c65 100644 --- a/server/internal/controller/admin/sys/im_invite.go +++ b/server/internal/controller/admin/sys/im_invite.go @@ -60,6 +60,11 @@ func (c *cImInvite) Revoke(ctx context.Context, req *iminvite.RevokeReq) (res *i return } +func (c *cImInvite) BindUser(ctx context.Context, req *iminvite.BindUserReq) (res *iminvite.BindUserRes, err error) { + err = service.SysImInvite().BindUser(ctx, &req.ImInviteBindUserInp) + return +} + func (c *cImInvite) BatchRevoke(ctx context.Context, req *iminvite.BatchRevokeReq) (res *iminvite.BatchRevokeRes, err error) { data, err := service.SysImInvite().BatchRevoke(ctx, &req.ImInviteBatchRevokeInp) if err != nil { diff --git a/server/internal/logic/sys/im_invite.go b/server/internal/logic/sys/im_invite.go index 2b8075c..d2f93e5 100644 --- a/server/internal/logic/sys/im_invite.go +++ b/server/internal/logic/sys/im_invite.go @@ -7,8 +7,10 @@ import ( "errors" "fmt" "hotgo/internal/library/imtenant" + "hotgo/internal/model/entity" "hotgo/internal/model/input/sysin" "hotgo/internal/service" + "net/url" "strconv" "strings" "time" @@ -44,18 +46,21 @@ type imInviteListRequest struct { } type imInviteCodeResponse struct { - ID int64 `json:"id"` - TenantID int64 `json:"tenant_id"` - TenantName string `json:"tenant_name"` - BatchID *int64 `json:"batch_id"` - Code string `json:"code"` - Status string `json:"status"` - ExpiresAt time.Time `json:"expires_at"` - UsageCount int `json:"usage_count"` - LastUsedAt *time.Time `json:"last_used_at"` - CreatedBy int64 `json:"created_by"` - Remark string `json:"remark"` - CreatedAt time.Time `json:"created_at"` + ID int64 `json:"id"` + TenantID int64 `json:"tenant_id"` + TenantName string `json:"tenant_name"` + BatchID *int64 `json:"batch_id"` + Code string `json:"code"` + Status string `json:"status"` + ExpiresAt time.Time `json:"expires_at"` + MaxUses int `json:"max_uses"` + UsageCount int `json:"usage_count"` + BoundUserID *int64 `json:"bound_user_id"` + BoundUserLabel string `json:"bound_user_label"` + LastUsedAt *time.Time `json:"last_used_at"` + CreatedBy int64 `json:"created_by"` + Remark string `json:"remark"` + CreatedAt time.Time `json:"created_at"` } type imInviteBatchResponse struct { @@ -202,6 +207,47 @@ func (s *sSysImInvite) Revoke(ctx context.Context, in *sysin.ImInviteRevokeInp) return err } +func (s *sSysImInvite) BindUser(ctx context.Context, in *sysin.ImInviteBindUserInp) (err error) { + if err = in.Filter(ctx); err != nil { + return err + } + scope, db, err := inviteDB(ctx, in.TenantId) + if err != nil { + return err + } + id, err := sysin.ParseOptionalInt64(in.Id) + if err != nil || id <= 0 { + return gerror.New("邀请码ID不能为空") + } + tenant, err := resolveImAdminTenant(ctx, scope.TenantId) + if err != nil { + return err + } + label, err := inviteBoundUserLabel(ctx, tenant, in.UserId) + if err != nil { + return err + } + now := time.Now().UTC().Round(time.Millisecond) + result, err := db.Exec(ctx, `UPDATE im_registration_invite_codes + SET bound_user_id=?,bound_user_label=?,updated_at=? + WHERE tenant_id=? AND id=? AND status='active' AND usage_count=0 AND (bound_user_id IS NULL OR bound_user_id=0)`, + in.UserId, label, now, scope.TenantId, id) + if err == nil { + if affected, _ := result.RowsAffected(); affected == 0 { + err = gerror.New("邀请码不存在、已使用、已绑定或不可绑定") + } + } + auditResult := "success" + if err != nil { + auditResult = "failed" + } + _ = writeImAdminAudit(ctx, scope.TenantId, "invite_bind_user", "invite_code", id, auditResult, g.Map{"user_id": in.UserId}) + if err != nil { + return err + } + return nil +} + func (s *sSysImInvite) BatchRevoke(ctx context.Context, in *sysin.ImInviteBatchRevokeInp) (res *sysin.ImInviteBatchModel, err error) { scope, db, err := inviteDB(ctx, in.TenantId) if err != nil { @@ -251,14 +297,14 @@ func (s *sSysImInvite) Export(ctx context.Context, in *sysin.ImInviteListInp) (e request.Response.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=registration-invite-codes-%s.csv", time.Now().Format("20060102-150405"))) _, _ = request.Response.Writer.Write([]byte{0xef, 0xbb, 0xbf}) writer := csv.NewWriter(request.Response.Writer) - _ = writer.Write([]string{"tenant_id", "tenant_name", "code", "status", "batch_id", "expires_at", "usage_count", "last_used_at", "created_by", "created_at", "remark"}) + _ = writer.Write([]string{"tenant_id", "tenant_name", "code", "status", "expires_at", "usage_count", "available_uses", "last_used_at", "created_by", "created_at", "remark"}) for offset := 0; ; offset += 100 { items, total, queryErr := queryInviteCodes(ctx, db, req, 100, offset) if queryErr != nil { return gerror.Wrap(queryErr, "导出邀请码失败") } for _, item := range items { - _ = writer.Write([]string{strconv.FormatInt(item.TenantID, 10), item.TenantName, item.Code, item.Status, formatOptionalInt64(item.BatchID), item.ExpiresAt.Format(time.RFC3339), strconv.Itoa(item.UsageCount), formatTime(item.LastUsedAt), strconv.FormatInt(item.CreatedBy, 10), item.CreatedAt.Format(time.RFC3339), item.Remark}) + _ = writer.Write([]string{strconv.FormatInt(item.TenantID, 10), item.TenantName, item.Code, item.Status, item.ExpiresAt.Format(time.RFC3339), strconv.Itoa(item.UsageCount), formatInviteAvailableUses(item.MaxUses, item.UsageCount), formatTime(item.LastUsedAt), strconv.FormatInt(item.CreatedBy, 10), item.CreatedAt.Format(time.RFC3339), item.Remark}) } writer.Flush() if writer.Error() != nil { @@ -300,10 +346,10 @@ func createSingleInvite(ctx context.Context, db gdb.DB, tenantID, operatorID int } now := time.Now().UTC().Round(time.Millisecond) _, err = db.Exec(ctx, `INSERT INTO im_registration_invite_codes - (id,tenant_id,batch_id,code,status,expires_at,usage_count,last_used_at,created_by,remark,created_at,updated_at) - VALUES(?,?,NULL,?,'active',?,0,NULL,?,?,?,?)`, id, tenantID, code, expiresAt, operatorID, remark, now, now) + (id,tenant_id,batch_id,code,status,expires_at,max_uses,usage_count,last_used_at,created_by,remark,created_at,updated_at) + VALUES(?,?,NULL,?,'active',?,1,0,NULL,?,?,?,?)`, id, tenantID, code, expiresAt, operatorID, remark, now, now) if err == nil { - return imInviteCodeResponse{ID: id, TenantID: tenantID, Code: code, Status: "active", ExpiresAt: expiresAt, CreatedBy: operatorID, Remark: remark, CreatedAt: now}, nil + return imInviteCodeResponse{ID: id, TenantID: tenantID, Code: code, Status: "active", ExpiresAt: expiresAt, MaxUses: 1, CreatedBy: operatorID, Remark: remark, CreatedAt: now}, nil } if !isDuplicateError(err) { return imInviteCodeResponse{}, err @@ -356,8 +402,8 @@ func createInviteBatch(ctx context.Context, db gdb.DB, tenantID, operatorID int6 } for i := range codes { if _, txErr := tx.Exec(`INSERT INTO im_registration_invite_codes - (id,tenant_id,batch_id,code,status,expires_at,usage_count,last_used_at,created_by,remark,created_at,updated_at) - VALUES(?,?,?,?,'active',?,0,NULL,?,?,?,?)`, codes[i].ID, tenantID, batchID, codes[i].Code, expiresAt, operatorID, remark, now, now); txErr != nil { + (id,tenant_id,batch_id,code,status,expires_at,max_uses,usage_count,last_used_at,created_by,remark,created_at,updated_at) + VALUES(?,?,?,?,'active',?,0,0,NULL,?,?,?,?)`, codes[i].ID, tenantID, batchID, codes[i].Code, expiresAt, operatorID, remark, now, now); txErr != nil { return txErr } codes[i].TenantID, codes[i].Status, codes[i].ExpiresAt, codes[i].CreatedBy, codes[i].Remark, codes[i].CreatedAt = tenantID, "active", expiresAt, operatorID, remark, now @@ -400,7 +446,7 @@ func queryInviteCodes(ctx context.Context, db gdb.DB, req imInviteListRequest, l } queryArgs := append(append([]any{}, args...), limit, offset) items := make([]imInviteCodeResponse, 0) - err = db.Raw(`SELECT c.id,c.tenant_id,t.name tenant_name,c.batch_id,c.code,c.status,c.expires_at,c.created_by,c.remark,c.created_at, + err = db.Raw(`SELECT c.id,c.tenant_id,t.name tenant_name,c.batch_id,c.code,c.status,c.expires_at,c.max_uses,c.bound_user_id,c.bound_user_label,c.created_by,c.remark,c.created_at, c.usage_count,c.last_used_at FROM im_registration_invite_codes c LEFT JOIN im_tenant t ON t.id=c.tenant_id @@ -521,10 +567,45 @@ func convertInviteCodes(items []imInviteCodeResponse) []*sysin.ImInviteCodeModel func convertInviteCode(item imInviteCodeResponse) *sysin.ImInviteCodeModel { return &sysin.ImInviteCodeModel{Id: strconv.FormatInt(item.ID, 10), BatchId: formatOptionalInt64(item.BatchID), Code: item.Code, TenantId: strconv.FormatInt(item.TenantID, 10), TenantName: item.TenantName, - Status: item.Status, ExpiresAt: gtime.New(item.ExpiresAt), UsageCount: item.UsageCount, LastUsedAt: convertTime(item.LastUsedAt), + Status: item.Status, ExpiresAt: gtime.New(item.ExpiresAt), MaxUses: item.MaxUses, UsageCount: item.UsageCount, + BoundUserId: formatOptionalInt64(item.BoundUserID), BoundUserLabel: item.BoundUserLabel, LastUsedAt: convertTime(item.LastUsedAt), CreatedBy: strconv.FormatInt(item.CreatedBy, 10), Remark: item.Remark, CreatedAt: gtime.New(item.CreatedAt)} } +func inviteBoundUserLabel(ctx context.Context, tenant *entity.ImTenant, userID int64) (string, error) { + if tenant == nil || tenant.Code == "" || userID <= 0 { + return "", gerror.New("绑定用户不合法") + } + values := addPageQuery(url.Values{}, 1, 1) + values.Set("user_ids", strconv.FormatInt(userID, 10)) + users, _, err := imAdminQueryList[sysin.ImUserListModel](ctx, tenant, + "/internal/admin-query/tenants/"+url.PathEscape(tenant.Code)+"/users", + values, + map[string]string{ + "public": "profileJson", + "trusted": "trustedJson", + "access": "accessJson", + }, + ) + if err != nil { + return "", err + } + if len(users) == 0 { + return "", gerror.New("绑定用户不存在") + } + user := users[0] + if user.Nickname != "" { + return fmt.Sprintf("%s(%s)", user.Nickname, user.UserId), nil + } + if user.Username != "" { + return fmt.Sprintf("%s(%s)", user.Username, user.UserId), nil + } + if user.Phone != "" { + return fmt.Sprintf("%s(%s)", user.Phone, user.UserId), nil + } + return user.UserId, nil +} + func convertInviteBatch(item *imInviteBatchResponse) *sysin.ImInviteBatchModel { if item == nil { return nil @@ -564,6 +645,17 @@ func formatTime(value *time.Time) string { return value.Format(time.RFC3339) } +func formatInviteAvailableUses(maxUses, usageCount int) string { + if maxUses <= 0 { + return "不限制" + } + available := maxUses - usageCount + if available < 0 { + available = 0 + } + return strconv.Itoa(available) +} + func batchID(batch *imInviteBatchResponse) int64 { if batch == nil { return 0 diff --git a/server/internal/model/input/sysin/im_invite.go b/server/internal/model/input/sysin/im_invite.go index 48df509..b7cb29f 100644 --- a/server/internal/model/input/sysin/im_invite.go +++ b/server/internal/model/input/sysin/im_invite.go @@ -105,24 +105,43 @@ type ImInviteRevokeInp struct { Id string `json:"id" v:"required#邀请码ID不能为空" dc:"邀请码ID"` } +type ImInviteBindUserInp struct { + TenantId int64 `json:"tenantId" dc:"IM租户ID"` + Id string `json:"id" v:"required#邀请码ID不能为空" dc:"邀请码ID"` + UserId int64 `json:"userId" v:"required|min:1#绑定用户不能为空|绑定用户不合法" dc:"绑定C端用户ID"` +} + +func (in *ImInviteBindUserInp) Filter(ctx context.Context) (err error) { + if _, err = ParseOptionalInt64(in.Id); err != nil { + return gerror.New("邀请码ID不能为空") + } + if in.UserId <= 0 { + return gerror.New("绑定用户不能为空") + } + return nil +} + type ImInviteBatchRevokeInp struct { TenantId int64 `json:"tenantId" dc:"IM租户ID"` BatchId string `json:"batchId" v:"required#批次ID不能为空" dc:"批次ID"` } type ImInviteCodeModel struct { - Id string `json:"id" dc:"ID"` - TenantId string `json:"tenantId" dc:"IM租户ID"` - TenantName string `json:"tenantName" dc:"IM租户名称"` - BatchId string `json:"batchId" dc:"批次ID"` - Code string `json:"code" dc:"邀请码"` - Status string `json:"status" dc:"状态"` - ExpiresAt *gtime.Time `json:"expiresAt" dc:"到期时间"` - UsageCount int `json:"usageCount" dc:"累计使用次数"` - LastUsedAt *gtime.Time `json:"lastUsedAt" dc:"最后使用时间"` - CreatedBy string `json:"createdBy" dc:"创建人"` - Remark string `json:"remark" dc:"备注"` - CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"` + Id string `json:"id" dc:"ID"` + TenantId string `json:"tenantId" dc:"IM租户ID"` + TenantName string `json:"tenantName" dc:"IM租户名称"` + BatchId string `json:"batchId" dc:"批次ID"` + Code string `json:"code" dc:"邀请码"` + Status string `json:"status" dc:"状态"` + ExpiresAt *gtime.Time `json:"expiresAt" dc:"到期时间"` + MaxUses int `json:"maxUses" dc:"最大可用次数,0表示不限制"` + UsageCount int `json:"usageCount" dc:"累计使用次数"` + BoundUserId string `json:"boundUserId" dc:"绑定用户ID"` + BoundUserLabel string `json:"boundUserLabel" dc:"绑定用户展示名"` + LastUsedAt *gtime.Time `json:"lastUsedAt" dc:"最后使用时间"` + CreatedBy string `json:"createdBy" dc:"创建人"` + Remark string `json:"remark" dc:"备注"` + CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"` } type ImInviteBatchModel struct { diff --git a/server/internal/service/sys.go b/server/internal/service/sys.go index 968c92e..b358819 100644 --- a/server/internal/service/sys.go +++ b/server/internal/service/sys.go @@ -209,6 +209,8 @@ type ( BatchView(ctx context.Context, in *sysin.ImInviteBatchViewInp) (res *sysin.ImInviteBatchDetailModel, err error) // Revoke 撤销邀请码 Revoke(ctx context.Context, in *sysin.ImInviteRevokeInp) (err error) + // BindUser 绑定邀请码注册后自动添加的C端用户 + BindUser(ctx context.Context, in *sysin.ImInviteBindUserInp) (err error) // BatchRevoke 批量撤销邀请码 BatchRevoke(ctx context.Context, in *sysin.ImInviteBatchRevokeInp) (res *sysin.ImInviteBatchModel, err error) // Export 导出邀请码 diff --git a/server/storage/data/generate/im_feedback_config_menu.sql b/server/storage/data/generate/im_feedback_config_menu.sql index a9753c8..bbfef77 100644 --- a/server/storage/data/generate/im_feedback_config_menu.sql +++ b/server/storage/data/generate/im_feedback_config_menu.sql @@ -8,14 +8,14 @@ WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'workbenchManager SET @workbenchRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'workbenchManager' LIMIT 1); INSERT INTO `hg_admin_menu` (`pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) -SELECT @workbenchRootId, '意见反馈配置', 'imFeedbackConfigIndex', 'feedback-config/index', '', 2, '', '/imFeedbackConfig/get', '', '/imFeedbackConfig/index', 1, 'workbenchManager', 0, 0, '', 0, 0, 0, 2, CONCAT('tr_', @workbenchRootId, ' '), 30, '', 1, @now, @now +SELECT @workbenchRootId, '意见反馈配置', 'imFeedbackConfigIndex', 'feedback-config/index', '', 2, '', '/imFeedbackConfig/get', '', '/imFeedbackConfig/index', 1, 'workbenchManager', 0, 0, '', 0, 0, 0, 2, CONCAT('tr_', @workbenchRootId, ' '), 40, '', 1, @now, @now WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imFeedbackConfigIndex'); SET @imFeedbackConfigId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imFeedbackConfigIndex' LIMIT 1); UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='feedback-config/index', `active_menu`='workbenchManager', - `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=30, + `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=40, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@imFeedbackConfigId; diff --git a/server/storage/data/generate/im_mailbox_config_menu.sql b/server/storage/data/generate/im_mailbox_config_menu.sql index 9a77336..e13044b 100644 --- a/server/storage/data/generate/im_mailbox_config_menu.sql +++ b/server/storage/data/generate/im_mailbox_config_menu.sql @@ -8,14 +8,14 @@ WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'workbenchManager SET @workbenchRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'workbenchManager' LIMIT 1); INSERT INTO `hg_admin_menu` (`pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) -SELECT @workbenchRootId, '总经理信箱配置', 'imMailboxConfigIndex', 'mailbox-config/index', '', 2, '', '/imMailboxConfig/get', '', '/imMailboxConfig/index', 1, 'workbenchManager', 0, 0, '', 0, 0, 0, 2, CONCAT('tr_', @workbenchRootId, ' '), 20, '', 1, @now, @now +SELECT @workbenchRootId, '总经理信箱配置', 'imMailboxConfigIndex', 'mailbox-config/index', '', 2, '', '/imMailboxConfig/get', '', '/imMailboxConfig/index', 1, 'workbenchManager', 0, 0, '', 0, 0, 0, 2, CONCAT('tr_', @workbenchRootId, ' '), 30, '', 1, @now, @now WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imMailboxConfigIndex'); SET @imMailboxConfigId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imMailboxConfigIndex' LIMIT 1); UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='mailbox-config/index', `active_menu`='workbenchManager', - `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=20, + `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=30, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@imMailboxConfigId; diff --git a/server/storage/data/generate/im_menu_hierarchy.sql b/server/storage/data/generate/im_menu_hierarchy.sql index f956eee..287aa3f 100644 --- a/server/storage/data/generate/im_menu_hierarchy.sql +++ b/server/storage/data/generate/im_menu_hierarchy.sql @@ -37,7 +37,6 @@ UPDATE `hg_admin_menu` SET `redirect`='/im/dashboard/index', `updated_at`=@now W UPDATE `hg_admin_menu` SET `pid`=@peopleId, `title`='用户管理', `path`='user/index', `level`=3, `tree`=CONCAT('tr_', @imRootId, ' tr_', @peopleId, ' '), `sort`=10, `updated_at`=@now WHERE `name`='imUserIndex'; UPDATE `hg_admin_menu` SET `pid`=@peopleId, `path`='user-policy-group/index', `level`=3, `tree`=CONCAT('tr_', @imRootId, ' tr_', @peopleId, ' '), `sort`=20, `updated_at`=@now WHERE `name`='imUserPolicyGroupIndex'; UPDATE `hg_admin_menu` SET `pid`=@peopleId, `title`='群组管理', `path`='group/index', `level`=3, `tree`=CONCAT('tr_', @imRootId, ' tr_', @peopleId, ' '), `sort`=30, `updated_at`=@now WHERE `name`='imGroupIndex'; -UPDATE `hg_admin_menu` SET `pid`=@peopleId, `path`='org/index', `level`=3, `tree`=CONCAT('tr_', @imRootId, ' tr_', @peopleId, ' '), `sort`=40, `updated_at`=@now WHERE `name`='imOrgIndex'; UPDATE `hg_admin_menu` SET `pid`=@peopleId, `title`='邀请码管理', `path`='registration-invite/index', `level`=3, `tree`=CONCAT('tr_', @imRootId, ' tr_', @peopleId, ' '), `sort`=50, `updated_at`=@now WHERE `name`='imRegistrationInviteIndex'; -- Content review. @@ -97,15 +96,17 @@ WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'workbenchManager SET @workbenchRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'workbenchManager' LIMIT 1); SET @workbenchId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imWorkbenchIndex' LIMIT 1); +SET @orgId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imOrgIndex' LIMIT 1); SET @mailboxConfigId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imMailboxConfigIndex' LIMIT 1); SET @feedbackConfigId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imFeedbackConfigIndex' LIMIT 1); UPDATE `hg_admin_menu` SET `pid`=0, `title`='工作台管理', `path`='/workbench', `icon`='AppstoreOutlined', `type`=1, `redirect`='/workbench/config/index', `component`='LAYOUT', `always_show`=1, `active_menu`='', `is_root`=1, `hidden`=0, `level`=1, `tree`=CONCAT('tr_', `id`, ' '), `sort`=126, `status`=1, `updated_at`=@now WHERE `id`=@workbenchRootId; UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `title`='工作台配置', `path`='config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=10, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@workbenchId; -UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='mailbox-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=20, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@mailboxConfigId; -UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='feedback-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=30, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@feedbackConfigId; +UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `title`='组织架构', `path`='org/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=20, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@orgId; +UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='mailbox-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=30, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@mailboxConfigId; +UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='feedback-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=40, `status`=1, `hidden`=0, `updated_at`=@now WHERE `id`=@feedbackConfigId; UPDATE `hg_admin_menu` SET `level`=3, `tree`=CONCAT('tr_', @workbenchRootId, ' tr_', @workbenchId, ' '), `updated_at`=@now WHERE `pid`=@workbenchId AND `type`=3; -UPDATE `hg_admin_menu` child JOIN `hg_admin_menu` page ON page.`id`=child.`pid` SET child.`level`=3, child.`tree`=CONCAT('tr_', @workbenchRootId, ' tr_', page.`id`, ' '), child.`updated_at`=@now WHERE page.`id` IN (@mailboxConfigId, @feedbackConfigId) AND child.`type`=3; +UPDATE `hg_admin_menu` child JOIN `hg_admin_menu` page ON page.`id`=child.`pid` SET child.`level`=3, child.`tree`=CONCAT('tr_', @workbenchRootId, ' tr_', page.`id`, ' '), child.`updated_at`=@now WHERE page.`id` IN (@orgId, @mailboxConfigId, @feedbackConfigId) AND child.`type`=3; INSERT IGNORE INTO `hg_admin_role_menu` (`role_id`, `menu_id`) -SELECT `role_id`, @workbenchRootId FROM `hg_admin_role_menu` WHERE `menu_id` IN (@workbenchId, @mailboxConfigId, @feedbackConfigId); +SELECT `role_id`, @workbenchRootId FROM `hg_admin_role_menu` WHERE `menu_id` IN (@workbenchId, @orgId, @mailboxConfigId, @feedbackConfigId); diff --git a/server/storage/data/generate/im_menu_title_repair.sql b/server/storage/data/generate/im_menu_title_repair.sql index da41e76..b37e3e7 100644 --- a/server/storage/data/generate/im_menu_title_repair.sql +++ b/server/storage/data/generate/im_menu_title_repair.sql @@ -48,6 +48,7 @@ UPDATE `hg_admin_menu` SET `title` = '创建邀请码' WHERE `name` = 'imInviteC UPDATE `hg_admin_menu` SET `title` = '批量创建邀请码' WHERE `name` = 'imInviteBatchCreate'; UPDATE `hg_admin_menu` SET `title` = '批次详情' WHERE `name` = 'imInviteBatchView'; UPDATE `hg_admin_menu` SET `title` = '撤销邀请码' WHERE `name` = 'imInviteRevoke'; +UPDATE `hg_admin_menu` SET `title` = '绑定用户' WHERE `name` = 'imInviteBindUser'; UPDATE `hg_admin_menu` SET `title` = '撤销批次邀请码' WHERE `name` = 'imInviteBatchRevoke'; UPDATE `hg_admin_menu` SET `title` = '导出邀请码' WHERE `name` = 'imInviteExport'; diff --git a/server/storage/data/generate/im_missing_menu_patch.sql b/server/storage/data/generate/im_missing_menu_patch.sql index a991f51..24584fa 100644 --- a/server/storage/data/generate/im_missing_menu_patch.sql +++ b/server/storage/data/generate/im_missing_menu_patch.sql @@ -87,9 +87,10 @@ WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name`='workbenchManager') SET @workbenchRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name`='workbenchManager' LIMIT 1); UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=10, `updated_at`=@now WHERE `id`=@workbenchId; -UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='mailbox-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=20, `updated_at`=@now WHERE `id`=@mailboxConfigId; -UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='feedback-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=30, `updated_at`=@now WHERE `id`=@feedbackConfigId; -UPDATE `hg_admin_menu` child JOIN `hg_admin_menu` page ON page.`id`=child.`pid` SET child.`level`=3, child.`tree`=CONCAT('tr_', @workbenchRootId, ' tr_', page.`id`, ' '), child.`updated_at`=@now WHERE page.`id` IN (@workbenchId, @mailboxConfigId, @feedbackConfigId) AND child.`type`=3; +UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `title`='组织架构', `path`='org/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=20, `updated_at`=@now WHERE `id`=@imOrgId; +UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='mailbox-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=30, `updated_at`=@now WHERE `id`=@mailboxConfigId; +UPDATE `hg_admin_menu` SET `pid`=@workbenchRootId, `path`='feedback-config/index', `active_menu`='workbenchManager', `level`=2, `tree`=CONCAT('tr_', @workbenchRootId, ' '), `sort`=40, `updated_at`=@now WHERE `id`=@feedbackConfigId; +UPDATE `hg_admin_menu` child JOIN `hg_admin_menu` page ON page.`id`=child.`pid` SET child.`level`=3, child.`tree`=CONCAT('tr_', @workbenchRootId, ' tr_', page.`id`, ' '), child.`updated_at`=@now WHERE page.`id` IN (@workbenchId, @imOrgId, @mailboxConfigId, @feedbackConfigId) AND child.`type`=3; INSERT IGNORE INTO `hg_admin_role_menu` (`role_id`, `menu_id`) -SELECT `role_id`, @workbenchRootId FROM `hg_admin_role_menu` WHERE `menu_id` IN (@workbenchId, @mailboxConfigId, @feedbackConfigId); +SELECT `role_id`, @workbenchRootId FROM `hg_admin_role_menu` WHERE `menu_id` IN (@workbenchId, @imOrgId, @mailboxConfigId, @feedbackConfigId); diff --git a/server/storage/data/generate/im_org_menu.sql b/server/storage/data/generate/im_org_menu.sql index e4737d1..34d59d1 100644 --- a/server/storage/data/generate/im_org_menu.sql +++ b/server/storage/data/generate/im_org_menu.sql @@ -1,40 +1,60 @@ SET @now := NOW(); SET @imRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imManager' LIMIT 1); +SET @workbenchRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'workbenchManager' LIMIT 1); INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) -SELECT NULL, @imRootId, '组织架构', 'imOrgIndex', 'org/index', '', '2', '', '/imOrg/departmentList', '', '/imOrg/index', '1', 'imManager', '0', '0', '', '0', '0', '0', '2', CONCAT('tr_', @imRootId, ' '), '65', '', '1', @now, @now +SELECT NULL, COALESCE(@workbenchRootId, @imRootId), '组织架构', 'imOrgIndex', 'org/index', '', '2', '', '/imOrg/departmentList', '', '/imOrg/index', '1', IF(@workbenchRootId IS NULL, 'imManager', 'workbenchManager'), '0', '0', '', '0', '0', '0', IF(@workbenchRootId IS NULL, '2', '2'), CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' '), '20', '', '1', @now, @now WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgIndex'); SET @imOrgId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imOrgIndex' LIMIT 1); +UPDATE `hg_admin_menu` +SET `pid` = COALESCE(@workbenchRootId, @imRootId), + `title` = '组织架构', + `path` = 'org/index', + `active_menu` = IF(@workbenchRootId IS NULL, 'imManager', 'workbenchManager'), + `level` = 2, + `tree` = CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' '), + `sort` = 20, + `status` = 1, + `hidden` = 0, + `updated_at` = @now +WHERE `id` = @imOrgId; + INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) -SELECT NULL, @imOrgId, '部门编辑', 'imOrgDepartmentEdit', '', '', '3', '', '/imOrg/departmentEdit', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '10', '', '1', @now, @now +SELECT NULL, @imOrgId, '部门编辑', 'imOrgDepartmentEdit', '', '', '3', '', '/imOrg/departmentEdit', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '10', '', '1', @now, @now WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgDepartmentEdit'); INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) -SELECT NULL, @imOrgId, '部门导出', 'imOrgDepartmentExport', '', '', '3', '', '/imOrg/departmentExport', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '15', '', '1', @now, @now +SELECT NULL, @imOrgId, '部门导出', 'imOrgDepartmentExport', '', '', '3', '', '/imOrg/departmentExport', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '15', '', '1', @now, @now WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgDepartmentExport'); INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) -SELECT NULL, @imOrgId, '部门删除', 'imOrgDepartmentDelete', '', '', '3', '', '/imOrg/departmentDelete', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '20', '', '1', @now, @now +SELECT NULL, @imOrgId, '部门删除', 'imOrgDepartmentDelete', '', '', '3', '', '/imOrg/departmentDelete', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '20', '', '1', @now, @now WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgDepartmentDelete'); INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) -SELECT NULL, @imOrgId, '成员列表', 'imOrgMemberList', '', '', '3', '', '/imOrg/memberList', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '30', '', '1', @now, @now +SELECT NULL, @imOrgId, '成员列表', 'imOrgMemberList', '', '', '3', '', '/imOrg/memberList', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '30', '', '1', @now, @now WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgMemberList'); INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) -SELECT NULL, @imOrgId, '成员编辑', 'imOrgMemberEdit', '', '', '3', '', '/imOrg/memberEdit', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '40', '', '1', @now, @now +SELECT NULL, @imOrgId, '成员编辑', 'imOrgMemberEdit', '', '', '3', '', '/imOrg/memberEdit', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '40', '', '1', @now, @now WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgMemberEdit'); INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) -SELECT NULL, @imOrgId, '成员导出', 'imOrgMemberExport', '', '', '3', '', '/imOrg/memberExport', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '45', '', '1', @now, @now +SELECT NULL, @imOrgId, '成员导出', 'imOrgMemberExport', '', '', '3', '', '/imOrg/memberExport', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '45', '', '1', @now, @now WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgMemberExport'); INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) -SELECT NULL, @imOrgId, '成员移除', 'imOrgMemberDelete', '', '', '3', '', '/imOrg/memberDelete', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '50', '', '1', @now, @now +SELECT NULL, @imOrgId, '成员移除', 'imOrgMemberDelete', '', '', '3', '', '/imOrg/memberDelete', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '50', '', '1', @now, @now WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgMemberDelete'); INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) -SELECT NULL, @imOrgId, '组织搜索', 'imOrgSearch', '', '', '3', '', '/imOrg/search', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @imOrgId, ' '), '60', '', '1', @now, @now +SELECT NULL, @imOrgId, '组织搜索', 'imOrgSearch', '', '', '3', '', '/imOrg/search', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), '60', '', '1', @now, @now WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imOrgSearch'); + +UPDATE `hg_admin_menu` +SET `level` = 3, + `tree` = CONCAT('tr_', COALESCE(@workbenchRootId, @imRootId), ' tr_', @imOrgId, ' '), + `updated_at` = @now +WHERE `pid` = @imOrgId AND `type` = 3; diff --git a/server/storage/data/generate/im_workbench_menu.sql b/server/storage/data/generate/im_workbench_menu.sql index 805d7d3..7cb95e5 100644 --- a/server/storage/data/generate/im_workbench_menu.sql +++ b/server/storage/data/generate/im_workbench_menu.sql @@ -22,6 +22,7 @@ WHERE @workbenchRootId IS NOT NULL AND NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imWorkbenchIndex'); SET @imWorkbenchId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imWorkbenchIndex' LIMIT 1); +SET @imOrgId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imOrgIndex' LIMIT 1); UPDATE `hg_admin_menu` SET `pid` = @workbenchRootId, `title` = '工作台配置', `path` = 'config/index', @@ -31,6 +32,14 @@ SET `pid` = @workbenchRootId, `title` = '工作台配置', `path` = 'config/inde `updated_at` = @now WHERE `id` = @imWorkbenchId; +UPDATE `hg_admin_menu` +SET `pid` = @workbenchRootId, `title` = '组织架构', `path` = 'org/index', + `type` = 2, `permissions` = '/imOrg/departmentList', `component` = '/imOrg/index', + `always_show` = 1, `active_menu` = 'workbenchManager', `is_root` = 0, `hidden` = 0, + `level` = 2, `tree` = CONCAT('tr_', @workbenchRootId, ' '), `sort` = 20, `status` = 1, + `updated_at` = @now +WHERE `id` = @imOrgId; + INSERT INTO `hg_admin_menu` (`pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`) SELECT @imWorkbenchId, item.title, item.name, '', '', 3, '', item.permissions, '', '', 1, '', 0, 0, '', 0, 1, 0, 3, CONCAT('tr_', @workbenchRootId, ' tr_', @imWorkbenchId, ' '), item.sort, '', 1, @now, @now FROM ( @@ -51,13 +60,13 @@ SET @feedbackConfigId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'imFee UPDATE `hg_admin_menu` SET `pid` = @workbenchRootId, `path` = 'mailbox-config/index', `active_menu` = 'workbenchManager', - `level` = 2, `tree` = CONCAT('tr_', @workbenchRootId, ' '), `sort` = 20, + `level` = 2, `tree` = CONCAT('tr_', @workbenchRootId, ' '), `sort` = 30, `status` = 1, `hidden` = 0, `updated_at` = @now WHERE `id` = @mailboxConfigId; UPDATE `hg_admin_menu` SET `pid` = @workbenchRootId, `path` = 'feedback-config/index', `active_menu` = 'workbenchManager', - `level` = 2, `tree` = CONCAT('tr_', @workbenchRootId, ' '), `sort` = 30, + `level` = 2, `tree` = CONCAT('tr_', @workbenchRootId, ' '), `sort` = 40, `status` = 1, `hidden` = 0, `updated_at` = @now WHERE `id` = @feedbackConfigId; @@ -66,11 +75,11 @@ JOIN `hg_admin_menu` page ON page.`id` = child.`pid` SET child.`level` = 3, child.`tree` = CONCAT('tr_', @workbenchRootId, ' tr_', page.`id`, ' '), child.`updated_at` = @now -WHERE page.`id` IN (@mailboxConfigId, @feedbackConfigId) +WHERE page.`id` IN (@imOrgId, @mailboxConfigId, @feedbackConfigId) AND child.`type` = 3; -- Preserve access for every role which could already open a workbench feature. INSERT IGNORE INTO `hg_admin_role_menu` (`role_id`, `menu_id`) SELECT `role_id`, @workbenchRootId FROM `hg_admin_role_menu` -WHERE `menu_id` IN (@imWorkbenchId, @mailboxConfigId, @feedbackConfigId); +WHERE `menu_id` IN (@imWorkbenchId, @imOrgId, @mailboxConfigId, @feedbackConfigId); diff --git a/server/storage/data/im_registration_invite_hotgo.sql b/server/storage/data/im_registration_invite_hotgo.sql index 6301bfc..eed8f80 100644 --- a/server/storage/data/im_registration_invite_hotgo.sql +++ b/server/storage/data/im_registration_invite_hotgo.sql @@ -81,6 +81,7 @@ CREATE TABLE IF NOT EXISTS im_registration_invite_batches ( id BIGINT NOT NULL, tenant_id BIGINT NOT NULL, quantity INT UNSIGNED NOT NULL, + max_uses_per_code INT UNSIGNED NOT NULL DEFAULT 0, idempotency_key VARCHAR(128) NOT NULL, expires_at DATETIME(3) NOT NULL, created_by BIGINT NOT NULL, @@ -99,6 +100,9 @@ CREATE TABLE IF NOT EXISTS im_registration_invite_codes ( code CHAR(5) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, status VARCHAR(16) NOT NULL, expires_at DATETIME(3) NOT NULL, + max_uses INT UNSIGNED NOT NULL DEFAULT 0, + bound_user_id BIGINT NULL, + bound_user_label VARCHAR(128) NOT NULL DEFAULT '', usage_count INT UNSIGNED NOT NULL DEFAULT 0, last_used_at DATETIME(3) NULL, created_by BIGINT NOT NULL, @@ -107,6 +111,7 @@ CREATE TABLE IF NOT EXISTS im_registration_invite_codes ( updated_at DATETIME(3) NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_im_registration_invite_code_tenant (tenant_id, code), + KEY idx_im_registration_invite_tenant_bound_user (tenant_id, bound_user_id), KEY idx_im_registration_invite_tenant_status_expires (tenant_id, status, expires_at), KEY idx_im_registration_invite_tenant_batch (tenant_id, batch_id), KEY idx_im_registration_invite_tenant_created (tenant_id, created_at) @@ -166,8 +171,9 @@ FROM ( SELECT '批量生成邀请码', 'imInviteBatchCreate', '/imInvite/batchCreate', 20 UNION ALL SELECT '批次详情', 'imInviteBatchView', '/imInvite/batchView', 30 UNION ALL SELECT '撤销邀请码', 'imInviteRevoke', '/imInvite/revoke', 40 UNION ALL - SELECT '批量撤销邀请码', 'imInviteBatchRevoke', '/imInvite/batchRevoke', 50 UNION ALL - SELECT '导出邀请码', 'imInviteExport', '/imInvite/export', 60 + SELECT '绑定用户', 'imInviteBindUser', '/imInvite/bindUser', 50 UNION ALL + SELECT '批量撤销邀请码', 'imInviteBatchRevoke', '/imInvite/batchRevoke', 60 UNION ALL + SELECT '导出邀请码', 'imInviteExport', '/imInvite/export', 70 ) p WHERE NOT EXISTS (SELECT 1 FROM hg_admin_menu m WHERE m.name = p.name); diff --git a/web/src/api/im/registrationInvite.ts b/web/src/api/im/registrationInvite.ts index 3bc8c48..9c192c5 100644 --- a/web/src/api/im/registrationInvite.ts +++ b/web/src/api/im/registrationInvite.ts @@ -40,6 +40,14 @@ export function Revoke(params) { }); } +export function BindUser(params) { + return http.request({ + url: '/imInvite/bindUser', + method: 'POST', + params, + }); +} + export function BatchRevoke(params) { return http.request({ url: '/imInvite/batchRevoke', diff --git a/web/src/views/im/registrationInvite/BatchViewModal.vue b/web/src/views/im/registrationInvite/BatchViewModal.vue index 5fc54e1..4928f0c 100644 --- a/web/src/views/im/registrationInvite/BatchViewModal.vue +++ b/web/src/views/im/registrationInvite/BatchViewModal.vue @@ -1,7 +1,6 @@ @@ -69,6 +70,7 @@ import { adaTableScrollX } from '@/utils/hotgo'; import CreateModal from './CreateModal.vue'; import BatchViewModal from './BatchViewModal.vue'; + import BindUserModal from './BindUserModal.vue'; const message = useMessage(); const dialog = useDialog(); @@ -78,6 +80,7 @@ const searchFormRef = ref({}); const createRef = ref(); const batchViewRef = ref(); + const bindUserRef = ref(); const isPlatformAdmin = computed(() => userStore.isCompanyDept); const tableColumns = computed(() => @@ -98,7 +101,7 @@ ); const actionColumn = reactive({ - width: 300, + width: 380, title: '操作', key: 'action', fixed: 'right', @@ -106,6 +109,12 @@ return h(TableAction as any, { style: 'button', actions: [ + { + label: '绑定用户', + onClick: handleBindUser.bind(null, record), + auth: ['/imInvite/bindUser'], + ifShow: () => record.status === 'active' && !record.boundUserId && Number(record.usageCount || 0) === 0, + }, { label: '批次', onClick: handleBatchView.bind(null, record), @@ -168,6 +177,10 @@ batchViewRef.value.openModal(Number(record.tenantId || selectedTenantId()), record.batchId); } + function handleBindUser(record: State) { + bindUserRef.value.openModal(Number(record.tenantId || selectedTenantId()), record.id); + } + function handleRevoke(record: State) { dialog.warning({ title: '确认撤销', @@ -185,7 +198,7 @@ function handleBatchRevoke(record: State) { dialog.warning({ title: '确认撤销批次', - content: `将撤销批次 ${record.batchId} 下仍有效的邀请码,已过期或已撤销的邀请码不受影响,是否确认?`, + content: '将撤销该批次下仍有效的邀请码,已过期或已撤销的邀请码不受影响,是否确认?', positiveText: '确认', negativeText: '取消', onPositiveClick: async () => { diff --git a/web/src/views/im/registrationInvite/model.ts b/web/src/views/im/registrationInvite/model.ts index 26272ae..4e74e04 100644 --- a/web/src/views/im/registrationInvite/model.ts +++ b/web/src/views/im/registrationInvite/model.ts @@ -16,7 +16,10 @@ export class State { public code = ''; public status = ''; public expiresAt = ''; + public maxUses = 0; public usageCount = 0; + public boundUserId = ''; + public boundUserLabel = ''; public lastUsedAt = ''; public createdBy = ''; public remark = ''; @@ -59,14 +62,6 @@ export const schemas = ref([ placeholder: '请选择状态', }, }, - { - field: 'batchId', - component: 'NInput', - label: '批次ID', - componentProps: { - placeholder: '请输入批次ID', - }, - }, { field: 'createdBy', component: 'NInput', @@ -102,9 +97,26 @@ export const columns = [ { title: '商户', key: 'tenantName', align: 'left', width: 160 }, { title: '邀请码', key: 'code', align: 'left', width: 120 }, { title: '状态', key: 'status', align: 'left', width: 110 }, - { title: '批次ID', key: 'batchId', align: 'left', width: 150 }, { title: '到期时间', key: 'expiresAt', align: 'left', width: 190 }, { title: '累计使用次数', key: 'usageCount', align: 'left', width: 130 }, + { + title: '可用次数', + key: 'availableUses', + align: 'left', + width: 110, + render(row: State) { + return inviteAvailableUsesLabel(row); + }, + }, + { + title: '绑定用户', + key: 'boundUserLabel', + align: 'left', + width: 180, + render(row: State) { + return row.boundUserLabel || row.boundUserId || '--'; + }, + }, { title: '最后使用时间', key: 'lastUsedAt', align: 'left', width: 190 }, { title: '创建人', key: 'createdBy', align: 'left', width: 110 }, { title: '创建时间', key: 'createdAt', align: 'left', width: 190 }, @@ -128,6 +140,13 @@ export function statusLabel(status: string) { return statusOptions.find((item) => item.value === status)?.label || status; } +export function inviteAvailableUsesLabel(row: State) { + if (!row.maxUses || Number(row.maxUses) <= 0) { + return '不限制'; + } + return `${Math.max(Number(row.maxUses) - Number(row.usageCount || 0), 0)}`; +} + export function localRfc3339(value: number | null) { if (!value) { return '';