feat: 新增商户管理员账号自动创建功能,优化租户权限控制
1. 后端实现租户创建时自动绑定创建管理员账号,支持超管/普通管理员权限区分 2. 前端适配超管权限控制业务库名、连接串等敏感字段展示与编辑 3. 优化租户数据库回退逻辑,未配置独立库时自动回落默认库 4. 调整租户列表与详情页的权限数据脱敏逻辑
This commit is contained in:
@@ -95,7 +95,12 @@ func tenantLink(ctx context.Context, tenant *entity.ImTenant) (string, error) {
|
|||||||
return normalizeMySQLTimeOptions(tenant.DbDsn)
|
return normalizeMySQLTimeOptions(tenant.DbDsn)
|
||||||
}
|
}
|
||||||
if tenant.DbName == "" {
|
if tenant.DbName == "" {
|
||||||
return "", gerror.New("租户业务库未配置")
|
// 未配置独立业务库的商户回落共享默认库
|
||||||
|
defaultLink := defaultDbLink(ctx)
|
||||||
|
if defaultLink == "" {
|
||||||
|
return "", gerror.New("默认数据库连接未配置,无法回落共享业务库")
|
||||||
|
}
|
||||||
|
return normalizeMySQLTimeOptions(defaultLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultLink := defaultDbLink(ctx)
|
defaultLink := defaultDbLink(ctx)
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ import (
|
|||||||
"hotgo/internal/library/hgorm/hook"
|
"hotgo/internal/library/hgorm/hook"
|
||||||
"hotgo/internal/library/imtenant"
|
"hotgo/internal/library/imtenant"
|
||||||
"hotgo/internal/model/entity"
|
"hotgo/internal/model/entity"
|
||||||
|
"hotgo/internal/model/input/adminin"
|
||||||
"hotgo/internal/model/input/sysin"
|
"hotgo/internal/model/input/sysin"
|
||||||
"hotgo/internal/service"
|
"hotgo/internal/service"
|
||||||
|
"hotgo/utility/tree"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/database/gdb"
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
@@ -32,6 +34,9 @@ const (
|
|||||||
tenantConfigRealnameReviewPolicy = "realname_review_policy"
|
tenantConfigRealnameReviewPolicy = "realname_review_policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// imManageRoleKey 商户级IM管理员角色标识
|
||||||
|
const imManageRoleKey = "imManage"
|
||||||
|
|
||||||
type imTenantConfigModeration struct {
|
type imTenantConfigModeration struct {
|
||||||
Id int64 `json:"id" orm:"id"`
|
Id int64 `json:"id" orm:"id"`
|
||||||
TenantId int64 `json:"tenant_id" orm:"tenant_id"`
|
TenantId int64 `json:"tenant_id" orm:"tenant_id"`
|
||||||
@@ -97,7 +102,7 @@ func (s *sSysImTenant) List(ctx context.Context, in *sysin.ImTenantListInp) (lis
|
|||||||
if in.Name != "" {
|
if in.Name != "" {
|
||||||
mod = mod.WhereLike(dao.ImTenant.Columns().Name, "%"+in.Name+"%")
|
mod = mod.WhereLike(dao.ImTenant.Columns().Name, "%"+in.Name+"%")
|
||||||
}
|
}
|
||||||
if in.DbName != "" {
|
if in.DbName != "" && isSuperTenantAdmin(ctx) {
|
||||||
mod = mod.WhereLike(dao.ImTenant.Columns().DbName, "%"+in.DbName+"%")
|
mod = mod.WhereLike(dao.ImTenant.Columns().DbName, "%"+in.DbName+"%")
|
||||||
}
|
}
|
||||||
if in.State != nil {
|
if in.State != nil {
|
||||||
@@ -122,6 +127,12 @@ func (s *sSysImTenant) List(ctx context.Context, in *sysin.ImTenantListInp) (lis
|
|||||||
if err = s.fillListConfig(ctx, list); err != nil {
|
if err = s.fillListConfig(ctx, list); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 业务库名仅超管可见,非超管脱敏返回
|
||||||
|
if !isSuperTenantAdmin(ctx) {
|
||||||
|
for _, item := range list {
|
||||||
|
item.DbName = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,8 +212,22 @@ func (s *sSysImTenant) Edit(ctx context.Context, in *sysin.ImTenantEditInp) (err
|
|||||||
return s.saveTenant(ctx, in)
|
return s.saveTenant(ctx, in)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isSuperTenantAdmin 当前操作者是否为admin超管
|
||||||
|
func isSuperTenantAdmin(ctx context.Context) bool {
|
||||||
|
return service.AdminMember().VerifySuperId(ctx, contexts.GetUserId(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *sSysImTenant) saveTenant(ctx context.Context, in *sysin.ImTenantEditInp) (err error) {
|
func (s *sSysImTenant) saveTenant(ctx context.Context, in *sysin.ImTenantEditInp) (err error) {
|
||||||
|
super := isSuperTenantAdmin(ctx)
|
||||||
|
isCreate := in.Id == 0
|
||||||
tenantID := in.Id
|
tenantID := in.Id
|
||||||
|
|
||||||
|
// 业务库名与独立连接串仅超管可维护,非超管提交一律强制忽略
|
||||||
|
if !super {
|
||||||
|
in.DbName = ""
|
||||||
|
in.DbDsn = ""
|
||||||
|
}
|
||||||
|
|
||||||
err = imtenant.RegistryDB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
err = imtenant.RegistryDB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||||
if err = s.checkCodeUnique(ctx, in.Id, in.Code); err != nil {
|
if err = s.checkCodeUnique(ctx, in.Id, in.Code); err != nil {
|
||||||
return
|
return
|
||||||
@@ -211,9 +236,19 @@ func (s *sSysImTenant) saveTenant(ctx context.Context, in *sysin.ImTenantEditInp
|
|||||||
userId := contexts.GetUserId(ctx)
|
userId := contexts.GetUserId(ctx)
|
||||||
if in.Id > 0 {
|
if in.Id > 0 {
|
||||||
in.UpdatedBy = userId
|
in.UpdatedBy = userId
|
||||||
|
data := g.Map{
|
||||||
|
dao.ImTenant.Columns().Code: in.Code,
|
||||||
|
dao.ImTenant.Columns().Name: in.Name,
|
||||||
|
dao.ImTenant.Columns().TenantDesc: in.TenantDesc,
|
||||||
|
dao.ImTenant.Columns().State: in.State,
|
||||||
|
dao.ImTenant.Columns().UpdatedBy: userId,
|
||||||
|
}
|
||||||
|
if super {
|
||||||
|
data[dao.ImTenant.Columns().DbName] = in.DbName
|
||||||
|
data[dao.ImTenant.Columns().DbDsn] = in.DbDsn
|
||||||
|
}
|
||||||
if _, err = s.Model(ctx).
|
if _, err = s.Model(ctx).
|
||||||
Fields(sysin.ImTenantUpdateFields{}).
|
WherePri(in.Id).Data(data).Update(); err != nil {
|
||||||
WherePri(in.Id).Data(in).Update(); err != nil {
|
|
||||||
return gerror.Wrap(err, "修改IM租户失败,请稍后重试!")
|
return gerror.Wrap(err, "修改IM租户失败,请稍后重试!")
|
||||||
}
|
}
|
||||||
return s.saveConfig(ctx, in, userId)
|
return s.saveConfig(ctx, in, userId)
|
||||||
@@ -234,10 +269,98 @@ func (s *sSysImTenant) saveTenant(ctx context.Context, in *sysin.ImTenantEditInp
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增商户时自动创建绑定的后台管理员账号,失败则补偿回滚商户数据
|
||||||
|
if isCreate {
|
||||||
|
if err = s.createTenantAdminMember(ctx, super, in, tenantID); err != nil {
|
||||||
|
s.rollbackTenantCreation(ctx, tenantID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ = publishImPolicyEvent(ctx, tenantID, in.Code, "tenant.features.updated", "moderation", 0)
|
_ = publishImPolicyEvent(ctx, tenantID, in.Code, "tenant.features.updated", "moderation", 0)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createTenantAdminMember 为新增商户自动创建后台管理员账号(默认绑定imManage角色)
|
||||||
|
func (s *sSysImTenant) createTenantAdminMember(ctx context.Context, super bool, in *sysin.ImTenantEditInp, tenantId int64) (err error) {
|
||||||
|
var role *entity.AdminRole
|
||||||
|
if err = dao.AdminRole.Ctx(ctx).
|
||||||
|
Fields(dao.AdminRole.Columns().Id).
|
||||||
|
Where(dao.AdminRole.Columns().Key, imManageRoleKey).
|
||||||
|
Where(dao.AdminRole.Columns().Status, consts.StatusEnabled).
|
||||||
|
OrderAsc(dao.AdminRole.Columns().Id).
|
||||||
|
Scan(&role); err != nil {
|
||||||
|
return gerror.Wrap(err, "查询IM管理员角色失败,请稍后重试!")
|
||||||
|
}
|
||||||
|
if role == nil || role.Id <= 0 {
|
||||||
|
return gerror.New("IM管理员角色(imManage)未配置,请联系运维")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 部门挂载:超管挂公司部门;非超管受部门数据权限限制,只能从自己部门的下级中选取
|
||||||
|
var deptId int64
|
||||||
|
if super {
|
||||||
|
companyDeptId, deptErr := dao.AdminDept.Ctx(ctx).
|
||||||
|
Fields(dao.AdminDept.Columns().Id).
|
||||||
|
Where(dao.AdminDept.Columns().Type, consts.DeptTypeCompany).
|
||||||
|
Where(dao.AdminDept.Columns().Status, consts.StatusEnabled).
|
||||||
|
OrderAsc(dao.AdminDept.Columns().Id).
|
||||||
|
Limit(1).
|
||||||
|
Value()
|
||||||
|
if deptErr != nil {
|
||||||
|
return gerror.Wrap(deptErr, "获取公司部门失败,请稍后重试!")
|
||||||
|
}
|
||||||
|
if companyDeptId.IsEmpty() {
|
||||||
|
return gerror.New("请先配置公司部门")
|
||||||
|
}
|
||||||
|
deptId = companyDeptId.Int64()
|
||||||
|
} else {
|
||||||
|
mb := contexts.GetUser(ctx)
|
||||||
|
if mb == nil || mb.DeptId <= 0 {
|
||||||
|
return gerror.New("获取当前用户部门失败")
|
||||||
|
}
|
||||||
|
subDeptId, deptErr := dao.AdminDept.Ctx(ctx).
|
||||||
|
Fields(dao.AdminDept.Columns().Id).
|
||||||
|
Where(dao.AdminDept.Columns().Status, consts.StatusEnabled).
|
||||||
|
WhereNot(dao.AdminDept.Columns().Id, mb.DeptId).
|
||||||
|
WhereLike(dao.AdminDept.Columns().Tree, "%"+tree.GetIdLabel(mb.DeptId)+"%").
|
||||||
|
OrderAsc(dao.AdminDept.Columns().Id).
|
||||||
|
Limit(1).
|
||||||
|
Value()
|
||||||
|
if deptErr != nil {
|
||||||
|
return gerror.Wrap(deptErr, "获取下级部门失败,请稍后重试!")
|
||||||
|
}
|
||||||
|
if subDeptId.IsEmpty() {
|
||||||
|
return gerror.New("当前部门下没有可用的下级部门来挂载商户管理员账号,请先配置下级部门")
|
||||||
|
}
|
||||||
|
deptId = subDeptId.Int64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 透传底层可读错误,便于前端定位
|
||||||
|
return service.AdminMember().Edit(ctx, &adminin.MemberEditInp{
|
||||||
|
TenantId: tenantId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Username: in.AdminUsername,
|
||||||
|
Password: in.AdminPassword,
|
||||||
|
RealName: in.Name + "管理员",
|
||||||
|
DeptId: deptId,
|
||||||
|
Status: consts.StatusEnabled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollbackTenantCreation 商户创建补偿:删除已写入的商户及配置记录
|
||||||
|
func (s *sSysImTenant) rollbackTenantCreation(ctx context.Context, tenantId int64) {
|
||||||
|
_ = imtenant.RegistryDB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||||
|
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||||||
|
WherePri(tenantId).Delete(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = s.configModel(ctx).
|
||||||
|
Where(dao.ImTenantConfig.Columns().TenantId, tenantId).Delete()
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *sSysImTenant) checkCodeUnique(ctx context.Context, id int64, code string) (err error) {
|
func (s *sSysImTenant) checkCodeUnique(ctx context.Context, id int64, code string) (err error) {
|
||||||
mod := s.Model(ctx).Where(dao.ImTenant.Columns().Code, code)
|
mod := s.Model(ctx).Where(dao.ImTenant.Columns().Code, code)
|
||||||
if id > 0 {
|
if id > 0 {
|
||||||
@@ -353,6 +476,11 @@ func (s *sSysImTenant) View(ctx context.Context, in *sysin.ImTenantViewInp) (res
|
|||||||
res.RealnameReviewPolicy = conf.RealnameReviewPolicy
|
res.RealnameReviewPolicy = conf.RealnameReviewPolicy
|
||||||
}
|
}
|
||||||
res.DbDsnConfigured = res.DbDsn != ""
|
res.DbDsnConfigured = res.DbDsn != ""
|
||||||
|
// 业务库名与独立连接串仅超管可见,非超管脱敏返回(保留是否已配置标记)
|
||||||
|
if !isSuperTenantAdmin(ctx) {
|
||||||
|
res.DbName = ""
|
||||||
|
res.DbDsn = ""
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,6 +520,10 @@ func (s *sSysImTenant) Usage(ctx context.Context, in *sysin.ImTenantUsageInp) (r
|
|||||||
DbDsnConfigured: tenant.DbDsn != "",
|
DbDsnConfigured: tenant.DbDsn != "",
|
||||||
Warnings: []string{},
|
Warnings: []string{},
|
||||||
}
|
}
|
||||||
|
// 业务库名仅超管可见,非超管脱敏返回
|
||||||
|
if !isSuperTenantAdmin(ctx) {
|
||||||
|
res.DbName = ""
|
||||||
|
}
|
||||||
|
|
||||||
db, _, dbErr := imtenant.DB(ctx, tenant.Id)
|
db, _, dbErr := imtenant.DB(ctx, tenant.Id)
|
||||||
if dbErr != nil {
|
if dbErr != nil {
|
||||||
|
|||||||
@@ -18,17 +18,6 @@ import (
|
|||||||
"github.com/gogf/gf/v2/os/gtime"
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImTenantUpdateFields 修改IM租户字段过滤
|
|
||||||
type ImTenantUpdateFields struct {
|
|
||||||
Code string `json:"code" dc:"租户编码"`
|
|
||||||
Name string `json:"name" dc:"租户名称"`
|
|
||||||
TenantDesc string `json:"tenantDesc" dc:"租户描述"`
|
|
||||||
DbName string `json:"dbName" dc:"业务库名"`
|
|
||||||
DbDsn string `json:"dbDsn" dc:"独立连接串"`
|
|
||||||
State int `json:"state" dc:"状态"`
|
|
||||||
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImTenantInsertFields 新增IM租户字段过滤
|
// ImTenantInsertFields 新增IM租户字段过滤
|
||||||
type ImTenantInsertFields struct {
|
type ImTenantInsertFields struct {
|
||||||
Code string `json:"code" dc:"租户编码"`
|
Code string `json:"code" dc:"租户编码"`
|
||||||
@@ -76,6 +65,8 @@ type ImTenantEditInp struct {
|
|||||||
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
|
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
|
||||||
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
|
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
|
||||||
RealnameReviewPolicy int `json:"realnameReviewPolicy" dc:"实名审核策略"`
|
RealnameReviewPolicy int `json:"realnameReviewPolicy" dc:"实名审核策略"`
|
||||||
|
AdminUsername string `json:"adminUsername" dc:"新增商户时自动创建的后台管理员账号名"`
|
||||||
|
AdminPassword string `json:"adminPassword" dc:"新增商户时自动创建的后台管理员初始密码"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (in *ImTenantEditInp) Filter(ctx context.Context) (err error) {
|
func (in *ImTenantEditInp) Filter(ctx context.Context) (err error) {
|
||||||
@@ -85,9 +76,6 @@ func (in *ImTenantEditInp) Filter(ctx context.Context) (err error) {
|
|||||||
if e := g.Validator().Rules("required|max-length:128").Data(in.Name).Messages("租户名称不能为空|租户名称不能超过128个字符").Run(ctx); e != nil {
|
if e := g.Validator().Rules("required|max-length:128").Data(in.Name).Messages("租户名称不能为空|租户名称不能超过128个字符").Run(ctx); e != nil {
|
||||||
return e.Current()
|
return e.Current()
|
||||||
}
|
}
|
||||||
if in.DbName == "" && in.DbDsn == "" {
|
|
||||||
return gerror.New("业务库名和独立连接串至少填写一个")
|
|
||||||
}
|
|
||||||
if !dict.HasOptionKey(consts.ImTenantStateOptions, in.State) {
|
if !dict.HasOptionKey(consts.ImTenantStateOptions, in.State) {
|
||||||
return gerror.New("租户状态不正确")
|
return gerror.New("租户状态不正确")
|
||||||
}
|
}
|
||||||
@@ -99,10 +87,20 @@ func (in *ImTenantEditInp) Filter(ctx context.Context) (err error) {
|
|||||||
if in.RealnameReviewPolicy != 0 && in.RealnameReviewPolicy != 1 {
|
if in.RealnameReviewPolicy != 0 && in.RealnameReviewPolicy != 1 {
|
||||||
return gerror.New("实名审核策略不正确")
|
return gerror.New("实名审核策略不正确")
|
||||||
}
|
}
|
||||||
|
if in.Id == 0 {
|
||||||
|
if e := g.Validator().Rules("required|length:3,32|regex:^[a-zA-Z0-9_-]+$").Data(in.AdminUsername).Messages("管理员账号不能为空|管理员账号长度应为3到32位|管理员账号仅支持字母、数字、下划线和横线").Run(ctx); e != nil {
|
||||||
|
return e.Current()
|
||||||
|
}
|
||||||
|
if e := g.Validator().Rules("required|length:6,32").Data(in.AdminPassword).Messages("管理员密码不能为空|管理员密码长度应为6到32位").Run(ctx); e != nil {
|
||||||
|
return e.Current()
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImTenantEditModel struct{}
|
type ImTenantEditModel struct {
|
||||||
|
AdminUsername string `json:"adminUsername" dc:"已创建的后台管理员账号名"`
|
||||||
|
}
|
||||||
|
|
||||||
// ImTenantViewInp 获取指定IM租户信息
|
// ImTenantViewInp 获取指定IM租户信息
|
||||||
type ImTenantViewInp struct {
|
type ImTenantViewInp struct {
|
||||||
|
|||||||
@@ -37,16 +37,17 @@
|
|||||||
<n-select
|
<n-select
|
||||||
v-model:value="formValue.state"
|
v-model:value="formValue.state"
|
||||||
:options="stateOptions"
|
:options="stateOptions"
|
||||||
|
:disabled="formValue.id === 0"
|
||||||
placeholder="请选择状态"
|
placeholder="请选择状态"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi span="1">
|
<n-gi span="1" v-if="isAdminSuper && formValue.id > 0">
|
||||||
<n-form-item label="业务库名" path="dbName">
|
<n-form-item label="业务库名" path="dbName">
|
||||||
<n-input placeholder="请输入业务库名" v-model:value="formValue.dbName" />
|
<n-input placeholder="请输入业务库名" v-model:value="formValue.dbName" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi span="2">
|
<n-gi span="2" v-if="isAdminSuper && formValue.id > 0">
|
||||||
<n-form-item label="独立连接串" path="dbDsn">
|
<n-form-item label="独立连接串" path="dbDsn">
|
||||||
<n-input
|
<n-input
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@@ -55,6 +56,25 @@
|
|||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
|
<n-gi span="1" v-if="formValue.id === 0">
|
||||||
|
<n-form-item label="管理员账号" path="adminUsername">
|
||||||
|
<n-input
|
||||||
|
placeholder="默认为商户编码+admin,可修改"
|
||||||
|
v-model:value="formValue.adminUsername"
|
||||||
|
@update:value="adminUsernameEdited = true"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi span="1" v-if="formValue.id === 0">
|
||||||
|
<n-form-item label="管理员密码" path="adminPassword">
|
||||||
|
<n-input
|
||||||
|
type="password"
|
||||||
|
show-password-on="click"
|
||||||
|
placeholder="请输入初始密码(6-32位)"
|
||||||
|
v-model:value="formValue.adminPassword"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
<n-gi span="1">
|
<n-gi span="1">
|
||||||
<n-form-item label="最大用户" path="maxUsers">
|
<n-form-item label="最大用户" path="maxUsers">
|
||||||
<n-input-number
|
<n-input-number
|
||||||
@@ -64,15 +84,6 @@
|
|||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi span="1">
|
|
||||||
<n-form-item label="最大群组" path="maxGroups">
|
|
||||||
<n-input-number
|
|
||||||
placeholder="0 表示不限"
|
|
||||||
:min="0"
|
|
||||||
v-model:value="formValue.maxGroups"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
</n-gi>
|
|
||||||
<n-gi span="1">
|
<n-gi span="1">
|
||||||
<n-form-item label="群成员上限" path="maxGroupMembers">
|
<n-form-item label="群成员上限" path="maxGroupMembers">
|
||||||
<n-input-number
|
<n-input-number
|
||||||
@@ -142,10 +153,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { useDictStore } from '@/store/modules/dict';
|
import { useDictStore } from '@/store/modules/dict';
|
||||||
import { Add, Edit, View } from '@/api/imTenant';
|
import { Add, Edit, View } from '@/api/imTenant';
|
||||||
import { newState, rules, State } from './model';
|
import { isAdminSuper, newState, rules, State } from './model';
|
||||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||||
import { useMessage } from 'naive-ui';
|
import { useMessage } from 'naive-ui';
|
||||||
import { adaModalWidth } from '@/utils/hotgo';
|
import { adaModalWidth } from '@/utils/hotgo';
|
||||||
@@ -168,16 +179,32 @@
|
|||||||
{ label: '自动审核,失败后转人工', value: 1 },
|
{ label: '自动审核,失败后转人工', value: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 新增时管理员账号默认跟随商户编码生成,手动修改后不再覆盖
|
||||||
|
const adminUsernameEdited = ref(false);
|
||||||
|
watch(
|
||||||
|
() => formValue.value.code,
|
||||||
|
(code) => {
|
||||||
|
if (formValue.value.id === 0 && !adminUsernameEdited.value) {
|
||||||
|
formValue.value.adminUsername = code ? code + 'admin' : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
function confirmForm(e) {
|
function confirmForm(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
formRef.value.validate((errors) => {
|
formRef.value.validate((errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
formBtnLoading.value = true;
|
formBtnLoading.value = true;
|
||||||
const submit = formValue.value.id > 0 ? Edit : Add;
|
const isCreate = formValue.value.id < 1;
|
||||||
|
const submit = isCreate ? Add : Edit;
|
||||||
submit(formValue.value)
|
submit(formValue.value)
|
||||||
.then((_res) => {
|
.then((_res) => {
|
||||||
message.success('操作成功');
|
message.success(
|
||||||
|
isCreate
|
||||||
|
? '商户与后台管理员账号 ' + formValue.value.adminUsername + ' 已创建'
|
||||||
|
: '操作成功'
|
||||||
|
);
|
||||||
closeForm();
|
closeForm();
|
||||||
emit('reloadTable');
|
emit('reloadTable');
|
||||||
})
|
})
|
||||||
@@ -201,6 +228,7 @@
|
|||||||
showModal.value = true;
|
showModal.value = true;
|
||||||
|
|
||||||
if (!state || state.id < 1) {
|
if (!state || state.id < 1) {
|
||||||
|
adminUsernameEdited.value = false;
|
||||||
formValue.value = newState(state);
|
formValue.value = newState(state);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
import { useDictStore } from '@/store/modules/dict';
|
import { useDictStore } from '@/store/modules/dict';
|
||||||
import { List, Restore, Status } from '@/api/imTenant';
|
import { List, Restore, Status } from '@/api/imTenant';
|
||||||
import { PlusOutlined } from '@vicons/antd';
|
import { PlusOutlined } from '@vicons/antd';
|
||||||
import { columns, loadOptions, schemas, State } from './model';
|
import { buildColumns, isAdminSuper, loadOptions, schemas, State } from './model';
|
||||||
import { adaTableScrollX } from '@/utils/hotgo';
|
import { adaTableScrollX } from '@/utils/hotgo';
|
||||||
import Edit from './edit.vue';
|
import Edit from './edit.vue';
|
||||||
import UsageModal from './usage.vue';
|
import UsageModal from './usage.vue';
|
||||||
@@ -116,14 +116,18 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 业务库名列仅admin超管可见
|
||||||
|
const columns = computed(() => buildColumns(isAdminSuper.value));
|
||||||
|
|
||||||
const scrollX = computed(() => {
|
const scrollX = computed(() => {
|
||||||
return adaTableScrollX(columns, actionColumn.width);
|
return adaTableScrollX(columns.value, actionColumn.width);
|
||||||
});
|
});
|
||||||
|
|
||||||
const [register, {}] = useForm({
|
const [register, {}] = useForm({
|
||||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||||
labelWidth: 90,
|
labelWidth: 90,
|
||||||
schemas,
|
// 业务库名搜索项仅admin超管可见
|
||||||
|
schemas: schemas.filter((item) => item.field !== 'dbName' || isAdminSuper.value),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载表格数据
|
// 加载表格数据
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ import { FormSchema } from '@/components/Form';
|
|||||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||||
import { renderPopoverMemberSumma, MemberSumma } from '@/utils';
|
import { renderPopoverMemberSumma, MemberSumma } from '@/utils';
|
||||||
import { useDictStore } from '@/store/modules/dict';
|
import { useDictStore } from '@/store/modules/dict';
|
||||||
|
import { useUserStore } from '@/store/modules/user';
|
||||||
|
|
||||||
const dict = useDictStore();
|
const dict = useDictStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
// 是否admin超管(业务库名/独立连接串仅超管可见可编辑)
|
||||||
|
export const isAdminSuper = computed(() => userStore.getUserInfo?.username === 'admin');
|
||||||
|
|
||||||
export class State {
|
export class State {
|
||||||
public id = 0; // ID
|
public id = 0; // ID
|
||||||
@@ -15,6 +20,8 @@ export class State {
|
|||||||
public tenantDesc = ''; // 商户描述
|
public tenantDesc = ''; // 商户描述
|
||||||
public dbName = ''; // 业务库名
|
public dbName = ''; // 业务库名
|
||||||
public dbDsn = ''; // 独立连接串
|
public dbDsn = ''; // 独立连接串
|
||||||
|
public adminUsername = ''; // 新增商户时创建的后台管理员账号
|
||||||
|
public adminPassword = ''; // 新增商户时创建的后台管理员初始密码
|
||||||
public state = 1; // 状态
|
public state = 1; // 状态
|
||||||
public maxUsers = 0; // 最大用户数
|
public maxUsers = 0; // 最大用户数
|
||||||
public maxGroups = 0; // 最大群组数
|
public maxGroups = 0; // 最大群组数
|
||||||
@@ -87,6 +94,18 @@ export const rules = {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
message: '请选择商户状态',
|
message: '请选择商户状态',
|
||||||
},
|
},
|
||||||
|
adminUsername: {
|
||||||
|
required: true,
|
||||||
|
trigger: ['blur', 'input'],
|
||||||
|
type: 'string',
|
||||||
|
message: '请输入管理员账号',
|
||||||
|
},
|
||||||
|
adminPassword: {
|
||||||
|
required: true,
|
||||||
|
trigger: ['blur', 'input'],
|
||||||
|
type: 'string',
|
||||||
|
message: '请输入管理员密码',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 表格搜索表单
|
// 表格搜索表单
|
||||||
@@ -138,7 +157,7 @@ export const schemas = ref<FormSchema[]>([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// 表格列
|
// 表格列
|
||||||
export const columns = [
|
const baseColumns = [
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
key: 'id',
|
key: 'id',
|
||||||
@@ -194,15 +213,6 @@ export const columns = [
|
|||||||
return row.maxUsers > 0 ? row.maxUsers : '不限';
|
return row.maxUsers > 0 ? row.maxUsers : '不限';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '最大群组',
|
|
||||||
key: 'maxGroups',
|
|
||||||
align: 'left',
|
|
||||||
width: 110,
|
|
||||||
render(row: State) {
|
|
||||||
return row.maxGroups > 0 ? row.maxGroups : '不限';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '群成员上限',
|
title: '群成员上限',
|
||||||
key: 'maxGroupMembers',
|
key: 'maxGroupMembers',
|
||||||
@@ -287,6 +297,14 @@ export const columns = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 业务库名列仅admin超管可见
|
||||||
|
export function buildColumns(isAdmin: boolean) {
|
||||||
|
if (isAdmin) {
|
||||||
|
return baseColumns;
|
||||||
|
}
|
||||||
|
return baseColumns.filter((item) => item.key !== 'dbName');
|
||||||
|
}
|
||||||
|
|
||||||
// 加载字典数据选项
|
// 加载字典数据选项
|
||||||
export function loadOptions() {
|
export function loadOptions() {
|
||||||
dict.loadOptions(['ImTenantStateOptions']);
|
dict.loadOptions(['ImTenantStateOptions']);
|
||||||
|
|||||||
Reference in New Issue
Block a user