diff --git a/server/api/admin/imconfiginternal/im_config.go b/server/api/admin/imconfiginternal/im_config.go
index 4b01c5c..9acd7e7 100644
--- a/server/api/admin/imconfiginternal/im_config.go
+++ b/server/api/admin/imconfiginternal/im_config.go
@@ -57,3 +57,14 @@ type ThirdPartyReq struct {
type ThirdPartyRes struct {
Values map[string]string `json:"values" dc:"配置值"`
}
+
+type TenantLoginIPWhitelistReq struct {
+ g.Meta `path:"/internal/im-config/tenants/by-id/{id}/login-ip-whitelist" method:"get" tags:"IM内部配置" summary:"获取租户渠道登录IP白名单"`
+ Id int64 `json:"id" in:"path" dc:"租户ID"`
+}
+
+type TenantLoginIPWhitelistRes struct {
+ TenantId int64 `json:"tenant_id" dc:"租户ID"`
+ AppList []string `json:"app_list" dc:"APP登录IP白名单"`
+ WebList []string `json:"web_list" dc:"Web登录IP白名单"`
+}
diff --git a/server/api/admin/imuserpolicygroup/im_user_policy_group.go b/server/api/admin/imuserpolicygroup/im_user_policy_group.go
index fc6b0ac..9398965 100644
--- a/server/api/admin/imuserpolicygroup/im_user_policy_group.go
+++ b/server/api/admin/imuserpolicygroup/im_user_policy_group.go
@@ -55,3 +55,16 @@ type MemberRemoveReq struct {
sysin.ImUserPolicyGroupMemberRemoveInp
}
type MemberRemoveRes struct{}
+
+type LoginIPWhitelistReq struct {
+ g.Meta `path:"/imUserPolicyGroup/loginIPWhitelist" method:"get" tags:"IM用户策略分组" summary:"获取租户登录IP白名单"`
+ sysin.ImTenantLoginIPWhitelistInp
+}
+type LoginIPWhitelistRes struct {
+ *sysin.ImTenantLoginIPWhitelistModel
+}
+type LoginIPWhitelistSaveReq struct {
+ g.Meta `path:"/imUserPolicyGroup/loginIPWhitelistSave" method:"post" tags:"IM用户策略分组" summary:"保存租户登录IP白名单"`
+ sysin.ImTenantLoginIPWhitelistSaveInp
+}
+type LoginIPWhitelistSaveRes struct{}
diff --git a/server/internal/consts/im_user.go b/server/internal/consts/im_user.go
index b9f1064..96aab16 100644
--- a/server/internal/consts/im_user.go
+++ b/server/internal/consts/im_user.go
@@ -20,6 +20,8 @@ const (
ImUserRegisterTypeAdmin = "admin"
ImUserRegisterTypeMobile = "mobile"
ImUserRegisterTypeInviteCode = "invite_code"
+ ImLoginChannelApp = "app"
+ ImLoginChannelWeb = "web"
)
var ImUserRegisterTypeOptions = []*model.Option{
diff --git a/server/internal/controller/admin/sys/im_user_policy_group.go b/server/internal/controller/admin/sys/im_user_policy_group.go
index 5e70617..baa0e85 100644
--- a/server/internal/controller/admin/sys/im_user_policy_group.go
+++ b/server/internal/controller/admin/sys/im_user_policy_group.go
@@ -64,3 +64,16 @@ func (c *cImUserPolicyGroup) MemberRemove(ctx context.Context, req *imuserpolicy
err = service.SysImUserPolicyGroup().MemberRemove(ctx, &req.ImUserPolicyGroupMemberRemoveInp)
return
}
+
+func (c *cImUserPolicyGroup) LoginIPWhitelist(ctx context.Context, req *imuserpolicygroup.LoginIPWhitelistReq) (res *imuserpolicygroup.LoginIPWhitelistRes, err error) {
+ data, err := service.SysImUserPolicyGroup().LoginIPWhitelist(ctx, &req.ImTenantLoginIPWhitelistInp)
+ if err != nil {
+ return nil, err
+ }
+ return &imuserpolicygroup.LoginIPWhitelistRes{ImTenantLoginIPWhitelistModel: data}, nil
+}
+
+func (c *cImUserPolicyGroup) LoginIPWhitelistSave(ctx context.Context, req *imuserpolicygroup.LoginIPWhitelistSaveReq) (res *imuserpolicygroup.LoginIPWhitelistSaveRes, err error) {
+ err = service.SysImUserPolicyGroup().LoginIPWhitelistSave(ctx, &req.ImTenantLoginIPWhitelistSaveInp)
+ return
+}
diff --git a/server/internal/controller/internalapi/imconfig/im_config.go b/server/internal/controller/internalapi/imconfig/im_config.go
index bbe9c1f..342ca5d 100644
--- a/server/internal/controller/internalapi/imconfig/im_config.go
+++ b/server/internal/controller/internalapi/imconfig/im_config.go
@@ -6,6 +6,7 @@ import (
"hotgo/internal/dao"
"hotgo/internal/library/imtenant"
"hotgo/internal/model/entity"
+ "hotgo/internal/service"
"strings"
"github.com/gogf/gf/v2/errors/gerror"
@@ -90,6 +91,17 @@ func (c *cConfig) ThirdParty(ctx context.Context, req *api.ThirdPartyReq) (res *
return &api.ThirdPartyRes{Values: values}, nil
}
+func (c *cConfig) TenantLoginIPWhitelist(ctx context.Context, req *api.TenantLoginIPWhitelistReq) (res *api.TenantLoginIPWhitelistRes, err error) {
+ if _, err = getTenant(ctx, dao.ImTenant.Columns().Id, req.Id); err != nil {
+ return nil, err
+ }
+ data, err := service.SysImUserPolicyGroup().LoginIPWhitelistByTenant(ctx, req.Id)
+ if err != nil {
+ return nil, err
+ }
+ return &api.TenantLoginIPWhitelistRes{TenantId: data.TenantId, AppList: data.AppList, WebList: data.WebList}, nil
+}
+
func getTenant(ctx context.Context, field string, value any) (*entity.ImTenant, error) {
var tenant *entity.ImTenant
if err := imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).Where(field, value).Scan(&tenant); err != nil {
diff --git a/server/internal/logic/sys/im_user_policy_group.go b/server/internal/logic/sys/im_user_policy_group.go
index e1daf71..7647a27 100644
--- a/server/internal/logic/sys/im_user_policy_group.go
+++ b/server/internal/logic/sys/im_user_policy_group.go
@@ -18,6 +18,8 @@ import (
type sSysImUserPolicyGroup struct{}
+const imTenantLoginIPWhitelistTable = "im_tenant_login_ip_whitelists"
+
func NewSysImUserPolicyGroup() *sSysImUserPolicyGroup { return &sSysImUserPolicyGroup{} }
func init() { service.RegisterSysImUserPolicyGroup(NewSysImUserPolicyGroup()) }
@@ -288,3 +290,85 @@ func (s *sSysImUserPolicyGroup) MemberRemove(ctx context.Context, in *sysin.ImUs
_ = writeImAdminAudit(ctx, tenantID, "im_user_policy_group_member_remove", "im_user_policy_group", groupID, "success", g.Map{"user_id": row[mc.UserId].Int64()})
return publishImPolicyEvent(ctx, tenantID, tenantCode, "user_policy_group.updated", "user_policy_groups", uint(groupID))
}
+
+func (s *sSysImUserPolicyGroup) LoginIPWhitelist(ctx context.Context, in *sysin.ImTenantLoginIPWhitelistInp) (res *sysin.ImTenantLoginIPWhitelistModel, err error) {
+ if err = in.Filter(ctx); err != nil {
+ return nil, err
+ }
+ tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId)
+ if err != nil {
+ return nil, err
+ }
+ return s.LoginIPWhitelistByTenant(ctx, tenantID)
+}
+
+func (s *sSysImUserPolicyGroup) LoginIPWhitelistByTenant(ctx context.Context, tenantID int64) (res *sysin.ImTenantLoginIPWhitelistModel, err error) {
+ if tenantID <= 0 {
+ return nil, gerror.New("租户ID不合法")
+ }
+ rows, err := imtenant.RegistryDB().Model(imTenantLoginIPWhitelistTable).Safe().Ctx(ctx).
+ Fields("channel", "ip_cidr").
+ Where("tenant_id", tenantID).
+ Where("status", consts.StatusEnabled).
+ WhereNull("deleted_at").
+ OrderAsc("channel").OrderAsc("id").All()
+ if err != nil {
+ return nil, gerror.Wrap(err, "获取租户登录IP白名单失败")
+ }
+ res = &sysin.ImTenantLoginIPWhitelistModel{
+ TenantId: tenantID,
+ AppList: make([]string, 0),
+ WebList: make([]string, 0),
+ }
+ for _, row := range rows {
+ switch row["channel"].String() {
+ case consts.ImLoginChannelApp:
+ res.AppList = append(res.AppList, row["ip_cidr"].String())
+ case consts.ImLoginChannelWeb:
+ res.WebList = append(res.WebList, row["ip_cidr"].String())
+ }
+ }
+ return res, nil
+}
+
+func (s *sSysImUserPolicyGroup) LoginIPWhitelistSave(ctx context.Context, in *sysin.ImTenantLoginIPWhitelistSaveInp) (err error) {
+ if err = in.Filter(ctx); err != nil {
+ return err
+ }
+ tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId)
+ if err != nil {
+ return err
+ }
+ now := gtime.Now()
+ operatorID := contexts.GetUserId(ctx)
+ db := imtenant.RegistryDB()
+ err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
+ if _, txErr := tx.Model(imTenantLoginIPWhitelistTable).Safe().Ctx(ctx).
+ Where("tenant_id", tenantID).WhereNull("deleted_at").
+ Data(g.Map{"status": consts.StatusDisable, "deleted_at": now, "deleted_by": operatorID, "updated_at": now, "updated_by": operatorID}).Update(); txErr != nil {
+ return txErr
+ }
+ for channel, values := range map[string][]string{
+ consts.ImLoginChannelApp: in.AppList,
+ consts.ImLoginChannelWeb: in.WebList,
+ } {
+ for _, value := range values {
+ if _, txErr := tx.Exec(`INSERT INTO im_tenant_login_ip_whitelists
+ (tenant_id,channel,ip_cidr,status,created_by,updated_by)
+ VALUES(?,?,?,?,?,?)
+ ON DUPLICATE KEY UPDATE status=VALUES(status),deleted_at=NULL,deleted_by=NULL,updated_at=CURRENT_TIMESTAMP(3),updated_by=VALUES(updated_by)`,
+ tenantID, channel, value, consts.StatusEnabled, operatorID, operatorID); txErr != nil {
+ return txErr
+ }
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ return gerror.Wrap(err, "保存租户登录IP白名单失败")
+ }
+ _ = writeImAdminAudit(ctx, tenantID, "im_tenant_login_ip_whitelist_save", "im_tenant", tenantID, "success", g.Map{
+ "app_count": len(in.AppList), "web_count": len(in.WebList),
+ })
+ return nil
+}
diff --git a/server/internal/model/input/sysin/im_policy_test.go b/server/internal/model/input/sysin/im_policy_test.go
index c1c21b4..1cbbd13 100644
--- a/server/internal/model/input/sysin/im_policy_test.go
+++ b/server/internal/model/input/sysin/im_policy_test.go
@@ -143,3 +143,21 @@ func TestImUserPolicyGroupInputFilter(t *testing.T) {
t.Fatal("无效用户ID应被拒绝")
}
}
+
+func TestImTenantLoginIPWhitelistSaveFilter(t *testing.T) {
+ in := &ImTenantLoginIPWhitelistSaveInp{
+ TenantId: 1,
+ AppList: []string{" 203.0.113.10 ", "2001:db8::/32", "203.0.113.10"},
+ WebList: []string{"198.51.100.7/24"},
+ }
+ if err := in.Filter(context.Background()); err != nil {
+ t.Fatalf("合法租户登录白名单不应报错: %v", err)
+ }
+ if len(in.AppList) != 2 || in.AppList[0] != "203.0.113.10" || in.WebList[0] != "198.51.100.0/24" {
+ t.Fatalf("IP白名单未正确规范化: app=%v web=%v", in.AppList, in.WebList)
+ }
+ in.AppList = []string{"not-an-ip"}
+ if err := in.Filter(context.Background()); err == nil {
+ t.Fatal("非法IP地址应被拒绝")
+ }
+}
diff --git a/server/internal/model/input/sysin/im_user_policy_group.go b/server/internal/model/input/sysin/im_user_policy_group.go
index 6fb35cd..a2ec573 100644
--- a/server/internal/model/input/sysin/im_user_policy_group.go
+++ b/server/internal/model/input/sysin/im_user_policy_group.go
@@ -2,6 +2,7 @@ package sysin
import (
"context"
+ "net/netip"
"regexp"
"strings"
@@ -155,3 +156,66 @@ type ImUserPolicyGroupMemberRemoveInp struct {
}
func (in *ImUserPolicyGroupMemberRemoveInp) Filter(ctx context.Context) error { return nil }
+
+type ImTenantLoginIPWhitelistInp struct {
+ TenantId int64 `json:"tenantId" dc:"租户ID"`
+}
+
+func (in *ImTenantLoginIPWhitelistInp) Filter(ctx context.Context) error {
+ if in.TenantId < 0 {
+ return gerror.New("租户ID不合法")
+ }
+ return nil
+}
+
+type ImTenantLoginIPWhitelistSaveInp struct {
+ TenantId int64 `json:"tenantId" dc:"租户ID"`
+ AppList []string `json:"appList" dc:"APP登录IP白名单"`
+ WebList []string `json:"webList" dc:"Web登录IP白名单"`
+}
+
+func (in *ImTenantLoginIPWhitelistSaveInp) Filter(ctx context.Context) (err error) {
+ if in.TenantId < 0 {
+ return gerror.New("租户ID不合法")
+ }
+ if in.AppList, err = normalizeTenantLoginIPList(in.AppList); err != nil {
+ return gerror.Wrap(err, "APP登录白名单不合法")
+ }
+ if in.WebList, err = normalizeTenantLoginIPList(in.WebList); err != nil {
+ return gerror.Wrap(err, "Web登录白名单不合法")
+ }
+ return nil
+}
+
+type ImTenantLoginIPWhitelistModel struct {
+ TenantId int64 `json:"tenantId" dc:"租户ID"`
+ AppList []string `json:"appList" dc:"APP登录IP白名单"`
+ WebList []string `json:"webList" dc:"Web登录IP白名单"`
+}
+
+func normalizeTenantLoginIPList(values []string) ([]string, error) {
+ if len(values) > 500 {
+ return nil, gerror.New("单个渠道最多配置500条IP或CIDR")
+ }
+ result := make([]string, 0, len(values))
+ seen := make(map[string]struct{}, len(values))
+ for _, value := range values {
+ value = strings.TrimSpace(value)
+ if value == "" {
+ continue
+ }
+ if prefix, err := netip.ParsePrefix(value); err == nil {
+ value = prefix.Masked().String()
+ } else if address, addressErr := netip.ParseAddr(value); addressErr == nil {
+ value = address.String()
+ } else {
+ return nil, gerror.Newf("%s 不是有效的IPv4、IPv6或CIDR", value)
+ }
+ if _, ok := seen[value]; ok {
+ continue
+ }
+ seen[value] = struct{}{}
+ result = append(result, value)
+ }
+ return result, nil
+}
diff --git a/server/internal/service/sys.go b/server/internal/service/sys.go
index fc01d35..968c92e 100644
--- a/server/internal/service/sys.go
+++ b/server/internal/service/sys.go
@@ -377,6 +377,9 @@ type (
MemberList(ctx context.Context, in *sysin.ImUserPolicyGroupMemberListInp) (list []*sysin.ImUserPolicyGroupMemberModel, totalCount int, err error)
MemberAdd(ctx context.Context, in *sysin.ImUserPolicyGroupMemberAddInp) (err error)
MemberRemove(ctx context.Context, in *sysin.ImUserPolicyGroupMemberRemoveInp) (err error)
+ LoginIPWhitelist(ctx context.Context, in *sysin.ImTenantLoginIPWhitelistInp) (res *sysin.ImTenantLoginIPWhitelistModel, err error)
+ LoginIPWhitelistByTenant(ctx context.Context, tenantId int64) (res *sysin.ImTenantLoginIPWhitelistModel, err error)
+ LoginIPWhitelistSave(ctx context.Context, in *sysin.ImTenantLoginIPWhitelistSaveInp) (err error)
}
ISysImSensitiveWord interface {
List(ctx context.Context, in *sysin.ImSensitiveWordListInp) (list []*sysin.ImSensitiveWordListModel, totalCount int, err error)
diff --git a/server/storage/data/generate/im_tenant_login_ip_whitelist.sql b/server/storage/data/generate/im_tenant_login_ip_whitelist.sql
new file mode 100644
index 0000000..2e7ab0e
--- /dev/null
+++ b/server/storage/data/generate/im_tenant_login_ip_whitelist.sql
@@ -0,0 +1,42 @@
+-- Tenant-scoped APP/Web login IP whitelist and admin permissions.
+-- Empty whitelist for a channel means unrestricted access.
+SET NAMES utf8mb4;
+USE `hotgo`;
+
+CREATE TABLE IF NOT EXISTS `im_tenant_login_ip_whitelists` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `tenant_id` bigint NOT NULL COMMENT 'IM tenant ID',
+ `channel` varchar(16) NOT NULL COMMENT 'app or web',
+ `ip_cidr` varchar(64) NOT NULL COMMENT 'IPv4, IPv6 or CIDR',
+ `status` tinyint NOT NULL DEFAULT 1 COMMENT '1 enabled, 2 disabled',
+ `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+ `created_by` bigint NOT NULL DEFAULT 0,
+ `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+ `updated_by` bigint NOT NULL DEFAULT 0,
+ `deleted_at` datetime(3) DEFAULT NULL,
+ `deleted_by` bigint DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_im_tenant_login_ip` (`tenant_id`, `channel`, `ip_cidr`),
+ KEY `idx_im_tenant_login_ip_active` (`tenant_id`, `channel`, `status`, `deleted_at`),
+ CONSTRAINT `chk_im_tenant_login_ip_channel` CHECK (`channel` IN ('app', 'web'))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Tenant APP/Web login IP whitelist';
+
+SET @now = NOW();
+SELECT @groupMenuId := `id` FROM `hg_admin_menu` WHERE `name` = 'imUserPolicyGroupIndex' 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, @groupMenuId, '查看租户登录IP白名单', 'imTenantLoginIPWhitelist', '', '', '3', '', '/imUserPolicyGroup/loginIPWhitelist', '', '', '1', '', '0', '0', '', '0', '1', '0', '4', CONCAT(`tree`, 'tr_', `id`, ' '), '9', '', '1', @now, @now
+FROM `hg_admin_menu` WHERE `id` = @groupMenuId
+ AND NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imTenantLoginIPWhitelist');
+
+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, @groupMenuId, '保存租户登录IP白名单', 'imTenantLoginIPWhitelistSave', '', '', '3', '', '/imUserPolicyGroup/loginIPWhitelistSave', '', '', '1', '', '0', '0', '', '0', '1', '0', '4', CONCAT(`tree`, 'tr_', `id`, ' '), '10', '', '1', @now, @now
+FROM `hg_admin_menu` WHERE `id` = @groupMenuId
+ AND NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imTenantLoginIPWhitelistSave');
+
+INSERT IGNORE INTO `hg_admin_role_menu` (`role_id`, `menu_id`)
+SELECT parent_role.`role_id`, child.`id`
+FROM `hg_admin_role_menu` parent_role
+JOIN `hg_admin_menu` parent ON parent.`id` = parent_role.`menu_id` AND parent.`name` = 'imUserPolicyGroupIndex'
+JOIN `hg_admin_menu` child ON child.`pid` = parent.`id`
+WHERE child.`name` IN ('imTenantLoginIPWhitelist', 'imTenantLoginIPWhitelistSave');
diff --git a/web/src/api/imUserPolicyGroup/index.ts b/web/src/api/imUserPolicyGroup/index.ts
index 82807d6..56f7045 100644
--- a/web/src/api/imUserPolicyGroup/index.ts
+++ b/web/src/api/imUserPolicyGroup/index.ts
@@ -16,3 +16,7 @@ export const MemberAdd = (params) =>
http.request({ url: '/imUserPolicyGroup/memberAdd', method: 'POST', params });
export const MemberRemove = (params) =>
http.request({ url: '/imUserPolicyGroup/memberRemove', method: 'POST', params });
+export const LoginIPWhitelist = (params) =>
+ http.request({ url: '/imUserPolicyGroup/loginIPWhitelist', method: 'GET', params });
+export const LoginIPWhitelistSave = (params) =>
+ http.request({ url: '/imUserPolicyGroup/loginIPWhitelistSave', method: 'POST', params });
diff --git a/web/src/views/imUserPolicyGroup/index.vue b/web/src/views/imUserPolicyGroup/index.vue
index 834ea62..4216155 100644
--- a/web/src/views/imUserPolicyGroup/index.vue
+++ b/web/src/views/imUserPolicyGroup/index.vue
@@ -9,6 +9,17 @@
@reset="reloadTable"
@keyup.enter="reloadTable"
/>
+
+ 租户渠道 IP 白名单 APP:{{
+ whitelistSummary.appList.length
+ ? `已配置 ${whitelistSummary.appList.length} 条`
+ : '未配置'
+ }} Web:{{
+ whitelistSummary.webList.length
+ ? `已配置 ${whitelistSummary.webList.length} 条`
+ : '未配置'
+ }}
+
- 新增分组
+
+
+ 新增分组
+
+
+ IP 白名单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 确定
+
+
+
+
(null),
activeGroupId = ref(0);
const dialogWidth = computed(() => adaModalWidth(820));
+ const whitelistDialogWidth = computed(() => adaModalWidth(760));
+ const whitelistSummary = reactive({
+ tenantId: 0,
+ appList: [] as string[],
+ webList: [] as string[],
+ });
+ const loginIPForm = reactive({ appText: '', webText: '' });
const statusOptions = dict.getOption('sys_normal_disable');
const emptyForm = () => ({
id: 0,
@@ -214,7 +286,11 @@
schemas,
});
const loadDataTable = (res) => List({ ...searchFormRef.value?.formModel, ...res });
- const reloadTable = () => actionRef.value?.reload();
+ const currentTenantId = () => Number(searchFormRef.value?.formModel?.tenantId || 0);
+ const reloadTable = () => {
+ actionRef.value?.reload();
+ refreshLoginIPSummary();
+ };
const actionColumn = reactive({
width: 300,
title: '操作',
@@ -252,6 +328,52 @@
.map((v) => v.trim())
.filter(Boolean);
}
+ async function fetchLoginIPWhitelist() {
+ const tenantId = currentTenantId();
+ if (userStore.isCompanyDept && tenantId <= 0) {
+ return null;
+ }
+ return await LoginIPWhitelist({ tenantId });
+ }
+ async function refreshLoginIPSummary() {
+ const result = await fetchLoginIPWhitelist();
+ if (!result) {
+ Object.assign(whitelistSummary, { tenantId: 0, appList: [], webList: [] });
+ return;
+ }
+ Object.assign(whitelistSummary, {
+ tenantId: Number(result.tenantId || 0),
+ appList: result.appList || [],
+ webList: result.webList || [],
+ });
+ }
+ async function openLoginIPWhitelist() {
+ const result = await fetchLoginIPWhitelist();
+ if (!result) {
+ message.warning('请先选择具体租户');
+ return;
+ }
+ Object.assign(loginIPForm, {
+ appText: (result.appList || []).join('\n'),
+ webText: (result.webList || []).join('\n'),
+ });
+ loginIPVisible.value = true;
+ }
+ async function saveLoginIPWhitelist() {
+ submitting.value = true;
+ try {
+ await LoginIPWhitelistSave({
+ tenantId: currentTenantId(),
+ appList: lines(loginIPForm.appText),
+ webList: lines(loginIPForm.webText),
+ });
+ message.success('租户渠道 IP 白名单保存成功');
+ loginIPVisible.value = false;
+ await refreshLoginIPSummary();
+ } finally {
+ submitting.value = false;
+ }
+ }
function openEdit(row?: any) {
editForm.value = row
? {
@@ -361,4 +483,7 @@
.member-toolbar {
margin-bottom: 16px;
}
+ .whitelist-summary {
+ margin-bottom: 16px;
+ }