2
0

feat:白名单界面

This commit is contained in:
zhongzm
2025-06-10 19:10:05 +08:00
parent c518ebd921
commit 83a8fb9a98

View File

@@ -0,0 +1,523 @@
<template>
<SimpleScrollbar>
<a-card
:bordered="false"
style="box-shadow: 0 2px 8px #f0f1f2; border-radius: 8px; padding: 0 24px; margin: 24px;"
>
<div>
<!-- 标题 -->
<h2 style="font-weight: bold; font-size: 22px; margin-bottom: 8px;">Access Control</h2>
<!-- 站点选择框 -->
<div style="display: flex; align-items: center; margin-bottom: 24px;">
<a-select
v-model:value="selectedSiteId"
:loading="siteLoading"
placeholder="请选择站点"
style="width: 200px; margin-right: 16px;"
@change="handleSiteChange"
>
<a-select-option v-for="site in siteList" :key="site.siteId" :value="site.siteId">
{{ site.name }}
</a-select-option>
</a-select>
</div>
<!-- Pre-Authentication Access -->
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 12px;">
<span>Pre-Authentication Access</span>
<a-switch v-model:checked="preAuthEnabled" />
<span>Enable</span>
<a-tooltip title="Pre-Authentication Access 说明">
<i class="anticon anticon-info-circle" style="color: #13c2c2; margin-left: 4px;"></i>
</a-tooltip>
</div>
<div v-if="preAuthEnabled">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;">
<span>Pre-Authentication Access List</span>
<a-button type="link" style="color: #13c2c2; font-weight: bold;" @click="onAddPreAuth"><i class="anticon anticon-plus-circle" /> Add</a-button>
</div>
<a-table
:columns="preAuthColumns"
:dataSource="preAuthList"
:pagination="false"
bordered
size="middle"
rowKey="key"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-tooltip title="删除">
<a-button
type="link"
size="small"
style="color: #ff4d4f"
@click="() => handleDeletePreAuth(record)"
>
<template #icon>
<DeleteOutlined />
</template>
</a-button>
</a-tooltip>
</template>
</template>
</a-table>
</div>
<!-- Authentication-Free Client -->
<div style="margin: 32px 0 16px 0; display: flex; align-items: center; gap: 12px;">
<span>Authentication-Free Client</span>
<a-switch v-model:checked="freeClientEnabled" />
<span>Enable</span>
<a-tooltip title="Authentication-Free Client 说明">
<i class="anticon anticon-info-circle" style="color: #13c2c2; margin-left: 4px;"></i>
</a-tooltip>
</div>
<div v-if="freeClientEnabled">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;">
<span>Authentication-Free Client List</span>
<a-button type="link" style="color: #13c2c2; font-weight: bold;" @click="onAddFreeClient"><i class="anticon anticon-plus-circle" /> Add</a-button>
</div>
<a-table
:columns="freeClientColumns"
:dataSource="freeClientList"
:pagination="false"
bordered
size="middle"
rowKey="key"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-tooltip title="删除">
<a-button
type="link"
size="small"
style="color: #ff4d4f"
@click="() => handleDeleteFreeClient(record)"
>
<template #icon>
<DeleteOutlined />
</template>
</a-button>
</a-tooltip>
</template>
</template>
</a-table>
</div>
<!-- 操作按钮 -->
<div style="margin-top: 32px; display: flex; gap: 16px;">
<a-button type="primary" @click="handleApply">Apply</a-button>
<a-button @click="handleCancel">Cancel</a-button>
</div>
</div>
</a-card>
<!-- 新增添加预认证白名单弹窗 -->
<a-modal
v-model:visible="addPreAuthVisible"
title="Add Pre-Authentication Access"
@ok="handleAddPreAuthOk"
@cancel="() => addPreAuthVisible = false"
destroyOnClose
>
<a-form layout="vertical">
<a-form-item label="TYPE">
<a-select v-model:value="addPreAuthType" style="width: 100%;">
<a-select-option :value="1">IP</a-select-option>
<a-select-option :value="2">URL</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="addPreAuthType === 1" label="IP">
<div style="display: flex; align-items: center; gap: 4px;">
<a-input v-model:value="addPreAuthIpParts[0]" style="width: 56px;" :maxlength="3" />
<span>.</span>
<a-input v-model:value="addPreAuthIpParts[1]" style="width: 56px;" :maxlength="3" />
<span>.</span>
<a-input v-model:value="addPreAuthIpParts[2]" style="width: 56px;" :maxlength="3" />
<span>.</span>
<a-input v-model:value="addPreAuthIpParts[3]" style="width: 56px;" :maxlength="3" />
<span>/</span>
<a-input v-model:value="addPreAuthIpParts[4]" style="width: 56px;" :maxlength="2" />
</div>
</a-form-item>
<a-form-item v-if="addPreAuthType === 2" label="URL">
<a-input-group compact>
<span style="display: inline-block; width: 90px; text-align: center; background: #f5f5f5; border: 1px solid #d9d9d9; border-right: none; border-radius: 6px 0 0 6px; line-height: 32px;">http(s)://</span>
<a-input v-model:value="addPreAuthValue" style="width: calc(100% - 90px); border-radius: 0 6px 6px 0;" placeholder="请输入URL" />
</a-input-group>
</a-form-item>
</a-form>
</a-modal>
<!-- 新增添加免认证白名单弹窗 -->
<a-modal
v-model:visible="addFreeClientVisible"
title="Add Authentication-Free Client"
@ok="handleAddFreeClientOk"
@cancel="() => addFreeClientVisible = false"
destroyOnClose
>
<a-form layout="vertical">
<a-form-item label="TYPE">
<a-select v-model:value="addFreeClientType" style="width: 100%;">
<a-select-option :value="3">IP</a-select-option>
<a-select-option :value="4">MAC</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="addFreeClientType === 3" label="IP">
<div style="display: flex; align-items: center; gap: 4px;">
<a-input v-model:value="addFreeClientIpParts[0]" style="width: 56px;" :maxlength="3" />
<span>.</span>
<a-input v-model:value="addFreeClientIpParts[1]" style="width: 56px;" :maxlength="3" />
<span>.</span>
<a-input v-model:value="addFreeClientIpParts[2]" style="width: 56px;" :maxlength="3" />
<span>.</span>
<a-input v-model:value="addFreeClientIpParts[3]" style="width: 56px;" :maxlength="3" />
</div>
</a-form-item>
<a-form-item v-if="addFreeClientType === 4" label="MAC">
<div style="display: flex; align-items: center; gap: 4px;">
<a-input v-model:value="addFreeClientMacParts[0]" style="width: 40px;" :maxlength="2" />
<span>-</span>
<a-input v-model:value="addFreeClientMacParts[1]" style="width: 40px;" :maxlength="2" />
<span>-</span>
<a-input v-model:value="addFreeClientMacParts[2]" style="width: 40px;" :maxlength="2" />
<span>-</span>
<a-input v-model:value="addFreeClientMacParts[3]" style="width: 40px;" :maxlength="2" />
<span>-</span>
<a-input v-model:value="addFreeClientMacParts[4]" style="width: 40px;" :maxlength="2" />
<span>-</span>
<a-input v-model:value="addFreeClientMacParts[5]" style="width: 40px;" :maxlength="2" />
</div>
</a-form-item>
</a-form>
</a-modal>
</SimpleScrollbar>
</template>
<script setup lang="ts">
import { ref, onMounted, watch, h } from 'vue'
import { fetchSiteList, fetchAccessControl, updateAccessControl } from '@/service/api/auth'
import { DeleteOutlined } from '@ant-design/icons-vue'
import { message, Modal } from 'ant-design-vue'
import { SimpleScrollbar } from '~/packages/materials/src';
const preAuthEnabled = ref(true)
const freeClientEnabled = ref(true)
const preAuthList = ref([]) // 预认证白名单列表
const freeClientList = ref([]) // 免认证白名单列表
const siteList = ref<{ siteId: string, name: string }[]>([])
const selectedSiteId = ref('')
const siteLoading = ref(false)
// 新增:添加预认证白名单弹窗相关变量
const addPreAuthVisible = ref(false)
const addPreAuthType = ref(1) // 1: IP, 2: URL
const addPreAuthValue = ref('')
const addPreAuthIpParts = ref(['', '', '', '', '']) // [ip1, ip2, ip3, ip4, mask]
// 新增:添加免认证白名单弹窗相关变量
const addFreeClientVisible = ref(false)
const addFreeClientType = ref(3) // 3: IP, 4: MAC
const addFreeClientIpParts = ref(['', '', '', ''])
const addFreeClientMacParts = ref(['', '', '', '', '', ''])
// 定义表格列配置
const preAuthColumns = [
{
title: 'TYPE',
dataIndex: 'type',
key: 'type',
customRender: ({ record }: { record: any }) => {
if (record.type === 1) return 'IP'
if (record.type === 2) return 'URL'
return record.type
}
},
{
title: 'INFORMATION',
dataIndex: 'url',
key: 'url',
customRender: ({ record }: { record: any }) => {
if (record.type === 1) return record.ip
if (record.type === 2) return record.url
return '-'
}
},
{
title: 'ACTION',
key: 'action',
width: 120,
fixed: 'right',
align: 'center',
customRender: ({ record }: { record: any }) => {
return h(
'a-button',
{
type: 'link',
size: 'small',
style: { color: '#ff4d4f' },
onClick: () => handleDeletePreAuth(record)
},
{
icon: () => h(DeleteOutlined)
}
)
}
}
]
const freeClientColumns = [
{
title: 'TYPE',
dataIndex: 'type',
key: 'type',
customRender: ({ record }: { record: any }) => {
if (record.type === 3) return 'IP'
if (record.type === 4) return 'MAC'
return record.type
}
},
{
title: 'INFORMATION',
dataIndex: 'clientIp',
key: 'clientIp',
customRender: ({ record }: { record: any }) => {
if (record.type === 3) return record.clientIp
if (record.type === 4) return record.clientMac
return '-'
}
},
{
title: 'ACTION',
key: 'action',
width: 120,
fixed: 'right',
align: 'center',
customRender: ({ record }: { record: any }) => {
return h(
'a-button',
{
type: 'link',
size: 'small',
style: { color: '#ff4d4f' },
onClick: () => handleDeleteFreeClient(record)
},
{
icon: () => h(DeleteOutlined)
}
)
}
}
]
const getSiteList = async () => {
siteLoading.value = true
const { data, error } = await fetchSiteList({ pageNum: 1, pageSize: 100 })
console.log('siteList接口返回', data, error)
if (!error) {
siteList.value = data.rows || []
console.log('siteList.value', siteList.value)
if (siteList.value.length > 0) {
selectedSiteId.value = siteList.value[0].siteId
console.log('getSiteList设置selectedSiteId', selectedSiteId.value)
await getAccessControl()
}
}
siteLoading.value = false
}
const getAccessControl = async () => {
console.log('getAccessControl调用selectedSiteId', selectedSiteId.value)
if (!selectedSiteId.value) return
const { data, error } = await fetchAccessControl(selectedSiteId.value)
console.log('accessControl接口返回', data, error)
if (!error && data) {
preAuthEnabled.value = !!data.preAuthAccessEnable
freeClientEnabled.value = !!data.freeAuthClientEnable
preAuthList.value = (data.preAuthAccessPolicies || []).map((item: any) => ({
key: String(item.idInt),
type: item.type,
ip: item.ip,
url: item.url,
...item
}))
freeClientList.value = (data.freeAuthClientPolicies || []).map((item: any) => ({
key: String(item.idInt),
type: item.type,
clientIp: item.clientIp,
clientMac: item.clientMac,
...item
}))
console.log('preAuthList', preAuthList.value)
console.log('freeClientList', freeClientList.value)
} else {
console.log('getAccessControl未进入赋值分支', { error, data })
}
console.log('getAccessControl结束', preAuthList.value, freeClientList.value)
}
const handleSiteChange = async (value: string) => {
selectedSiteId.value = value
// 不需要手动调用 getAccessControlwatch 会自动触发
}
const onAddPreAuth = () => {
addPreAuthType.value = 1
addPreAuthValue.value = ''
addPreAuthIpParts.value = ['', '', '', '', '']
addPreAuthVisible.value = true
}
const handleAddPreAuthOk = () => {
if (addPreAuthType.value === 1) {
// 拼接IP
const ip = addPreAuthIpParts.value.slice(0, 4).join('.')
const mask = addPreAuthIpParts.value[4]
const ipValue = mask ? `${ip}/${mask}` : ip
preAuthList.value.push({
key: Date.now() + Math.random(), // 简单唯一key
type: 1,
ip: ipValue,
url: null
})
} else {
// 只保存输入框内容不带http(s)://
preAuthList.value.push({
key: Date.now() + Math.random(),
type: 2,
ip: null,
url: addPreAuthValue.value
})
}
addPreAuthVisible.value = false
}
const onAddFreeClient = () => {
addFreeClientType.value = 3
addFreeClientIpParts.value = ['', '', '', '']
addFreeClientMacParts.value = ['', '', '', '', '', '']
addFreeClientVisible.value = true
}
const handleAddFreeClientOk = () => {
if (addFreeClientType.value === 3) {
// 拼接IP
const ip = addFreeClientIpParts.value.join('.')
freeClientList.value.push({
key: Date.now() + Math.random(),
type: 3,
clientIp: ip,
clientMac: null
})
} else {
// 拼接MAC
const mac = addFreeClientMacParts.value.map(x => x.padStart(2, '0')).join('-')
freeClientList.value.push({
key: Date.now() + Math.random(),
type: 4,
clientIp: null,
clientMac: mac
})
}
addFreeClientVisible.value = false
}
const handleDeletePreAuth = (record: any) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除该条记录吗?',
okText: '确认',
cancelText: '取消',
onOk: () => {
preAuthList.value = preAuthList.value.filter(item => item.key !== record.key)
}
})
}
const handleDeleteFreeClient = (record: any) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除该条记录吗?',
okText: '确认',
cancelText: '取消',
onOk: () => {
freeClientList.value = freeClientList.value.filter(item => item.key !== record.key)
}
})
}
// 监听 selectedSiteId变化时自动拉取白名单数据
watch(selectedSiteId, (val) => {
console.log('watch触发selectedSiteId变为', val)
if (val) getAccessControl()
})
// 在 script setup 中添加提交函数
const handleApply = async () => {
if (!selectedSiteId.value) {
message.warning('请先选择站点')
return
}
try {
// 组装提交数据
const submitData = {
preAuthAccessEnable: preAuthEnabled.value,
freeAuthClientEnable: freeClientEnabled.value,
preAuthAccessPolicies: preAuthList.value.map(item => ({
type: item.type,
ip: item.type === 1 ? item.ip : undefined,
url: item.type === 2 ? item.url : undefined
})),
freeAuthClientPolicies: freeClientList.value.map(item => ({
type: item.type,
clientIp: item.type === 3 ? item.clientIp : undefined,
clientMac: item.type === 4 ? item.clientMac : undefined
}))
}
// 调用更新接口
const { error } = await updateAccessControl(selectedSiteId.value, submitData)
if (error) {
message.error('提交失败')
return
}
message.success('提交成功')
// 重新获取最新数据
await getAccessControl()
} catch (e) {
console.error('提交出错:', e)
message.error('提交失败')
}
}
// 在 script setup 中添加 Cancel 处理函数
const handleCancel = async () => {
if (!selectedSiteId.value) {
message.warning('请先选择站点')
return
}
try {
await getAccessControl()
message.success('已刷新数据')
} catch (e) {
console.error('刷新数据出错:', e)
message.error('刷新数据失败')
}
}
onMounted(async () => {
console.log('onMounted执行')
await getSiteList()
console.log('onMounted后selectedSiteId', selectedSiteId.value)
if (selectedSiteId.value) {
await getAccessControl()
}
})
</script>
<style scoped>
.anticon-info-circle {
font-size: 18px;
}
</style>