feat: add tenant login IP whitelist management

This commit is contained in:
richjohnrambo
2026-09-09 09:39:55 +08:00
parent 99307f1c98
commit 964746c19b
12 changed files with 400 additions and 9 deletions
+4
View File
@@ -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 });
+134 -9
View File
@@ -9,6 +9,17 @@
@reset="reloadTable"
@keyup.enter="reloadTable"
/>
<n-alert type="info" :show-icon="true" class="whitelist-summary">
租户渠道 IP 白名单 APP:{{
whitelistSummary.appList.length
? `已配置 ${whitelistSummary.appList.length} 条`
: '未配置'
}} Web:{{
whitelistSummary.webList.length
? `已配置 ${whitelistSummary.webList.length} 条`
: '未配置'
}}
</n-alert>
<BasicTable
ref="actionRef"
:columns="columns"
@@ -18,17 +29,67 @@
:scroll-x="scrollX"
:resizeHeightOffset="-10000"
>
<template #tableTitle
><n-button
type="primary"
:disabled="!hasPermission(['/imUserPolicyGroup/edit'])"
@click="openEdit()"
>新增分组</n-button
></template
>
<template #tableTitle>
<n-space>
<n-button
type="primary"
:disabled="!hasPermission(['/imUserPolicyGroup/edit'])"
@click="openEdit()"
>新增分组</n-button
>
<n-button
:disabled="!hasPermission(['/imUserPolicyGroup/loginIPWhitelist'])"
@click="openLoginIPWhitelist"
>
<template #icon
><n-icon><ShieldCheckmarkOutline /></n-icon
></template>
IP 白名单
</n-button>
</n-space>
</template>
</BasicTable>
</n-card>
<n-modal
v-model:show="loginIPVisible"
preset="dialog"
title="租户渠道 IP 白名单"
:show-icon="false"
:style="{ width: whitelistDialogWidth }"
>
<n-form label-placement="top">
<n-form-item label="APP 登录白名单">
<n-input
v-model:value="loginIPForm.appText"
type="textarea"
:rows="7"
placeholder="每行一个 IPv4、IPv6 或 CIDR,例如 203.0.113.10"
/>
</n-form-item>
<n-form-item label="Web 登录白名单">
<n-input
v-model:value="loginIPForm.webText"
type="textarea"
:rows="7"
placeholder="每行一个 IPv4、IPv6 或 CIDR,例如 198.51.100.0/24"
/>
</n-form-item>
</n-form>
<template #action>
<n-space>
<n-button @click="loginIPVisible = false">取消</n-button>
<n-button
type="primary"
:disabled="!hasPermission(['/imUserPolicyGroup/loginIPWhitelistSave'])"
:loading="submitting"
@click="saveLoginIPWhitelist"
>确定</n-button
>
</n-space>
</template>
</n-modal>
<n-modal
v-model:show="editVisible"
preset="dialog"
@@ -160,6 +221,7 @@
import { useDictStore } from '@/store/modules/dict';
import { usePermission } from '@/hooks/web/usePermission';
import { useUserStore } from '@/store/modules/user';
import { ShieldCheckmarkOutline } from '@vicons/ionicons5';
import { adaModalWidth, adaTableScrollX } from '@/utils/hotgo';
import {
Delete,
@@ -168,6 +230,8 @@
MemberAdd,
MemberList,
MemberRemove,
LoginIPWhitelist,
LoginIPWhitelistSave,
Status,
} from '@/api/imUserPolicyGroup';
import { columns, defaultPolicy, loadOptions, schemas } from './model';
@@ -184,10 +248,18 @@
memberTableRef = ref();
const editVisible = ref(false),
memberVisible = ref(false),
loginIPVisible = ref(false),
submitting = ref(false),
memberUserId = ref<number | null>(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;
}
</style>