Files
tyapi-frontend/src/pages/api/WhiteList.vue
2025-11-24 16:06:44 +08:00

438 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<ListPageLayout
title="白名单管理"
subtitle="管理您的API访问IP白名单最多可添加10个IP地址"
>
<template #actions>
<el-button type="primary" @click="showAddForm = true">
<PlusIcon class="w-4 h-4 mr-1" />
添加IP地址
</el-button>
</template>
<template #filters>
<FilterSection>
<FilterItem label="IP地址搜索">
<el-input
v-model="filters.keyword"
placeholder="输入IP地址进行搜索"
clearable
@input="handleSearch"
class="w-full"
>
<template #prefix>
<ComputerDesktopIcon class="w-4 h-4 text-gray-400" />
</template>
</el-input>
</FilterItem>
<FilterItem label="状态筛选">
<el-select
v-model="filters.status"
placeholder="选择状态"
clearable
@change="handleFilterChange"
class="w-full"
>
<el-option label="全部" value="" />
<el-option label="已添加" value="active" />
<el-option label="待添加" value="pending" />
</el-select>
</FilterItem>
<template #stats>
共找到 {{ whiteListData.length }}/10 个IP地址
</template>
<template #buttons>
<el-button @click="resetFilters">重置筛选</el-button>
<el-button type="primary" @click="loadWhiteList">应用筛选</el-button>
</template>
</FilterSection>
</template>
<template #table>
<div v-if="loading" class="flex justify-center items-center py-12">
<el-loading size="large" />
</div>
<div v-else-if="whiteListData.length === 0" class="text-center py-12">
<el-empty description="暂无白名单IP地址">
<template #image>
<ShieldCheckIcon class="w-16 h-16 text-gray-400" />
</template>
<p class="text-gray-500 mt-2">添加IP地址到白名单以允许API访问</p>
<el-button type="primary" @click="showAddForm = true" class="mt-4">
<PlusIcon class="w-4 h-4 mr-1" />
添加第一个IP地址
</el-button>
</el-empty>
</div>
<div v-else class="white-list-table">
<el-table :data="whiteListData" stripe>
<el-table-column prop="ip_address" label="IP地址" min-width="200">
<template #default="{ row }">
<div class="flex items-center">
<ComputerDesktopIcon class="w-4 h-4 mr-2 text-blue-500" />
<span class="font-mono text-sm">{{ row.ip_address }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="created_at" label="添加时间" min-width="180">
<template #default="{ row }">
<div class="flex items-center">
<CalendarIcon class="w-4 h-4 mr-2 text-gray-400" />
<span class="text-sm">{{ formatDate(row.created_at) }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="状态" width="120">
<template #default>
<el-tag type="success" size="small">
<span class="flex items-center"><ShieldCheckIcon class="w-3 h-3 mr-1" />已添加</span>
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button
type="danger"
size="small"
@click="handleDeleteIP(row.ip_address)"
:loading="deleteLoading === row.ip_address"
>
<TrashIcon class="w-3 h-3 mr-1" />
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<template #extra>
<!-- 添加IP地址弹窗 -->
<el-dialog
v-model="showAddForm"
title="添加IP地址"
width="500px"
:close-on-click-modal="false"
>
<el-form
ref="addFormRef"
:model="addForm"
:rules="addFormRules"
label-width="100px"
>
<el-form-item label="IP地址" prop="ipAddress">
<el-input
v-model="addForm.ipAddress"
placeholder="请输入IP地址192.168.1.1"
:disabled="whiteListData.length >= 10"
>
<template #prefix>
<ComputerDesktopIcon class="w-4 h-4 text-gray-400" />
</template>
</el-input>
</el-form-item>
<el-form-item v-if="whiteListData.length >= 10">
<el-alert
title="白名单已满"
type="warning"
:closable="false"
show-icon
>
<template #default>
<p>您已达到白名单数量上限10请先删除不需要的IP地址后再添加新的</p>
</template>
</el-alert>
</el-form-item>
</el-form>
<template #footer>
<div class="flex justify-end gap-2">
<el-button @click="showAddForm = false">取消</el-button>
<el-button
type="primary"
@click="handleAddIP"
:loading="addLoading"
:disabled="whiteListData.length >= 10"
>
添加
</el-button>
</div>
</template>
</el-dialog>
<!-- 使用说明卡片 -->
<div class="mt-6">
<el-card class="help-card">
<template #header>
<div class="flex items-center">
<QuestionMarkCircleIcon class="w-5 h-5 mr-2 text-orange-600" />
<span class="text-lg font-semibold">使用说明</span>
</div>
</template>
<div class="help-content">
<div class="help-item">
<h4 class="help-title">什么是白名单</h4>
<p class="help-text">
白名单是API访问的安全机制只有添加到白名单中的IP地址才能调用您的API接口
</p>
</div>
<div class="help-item">
<h4 class="help-title">如何添加IP地址</h4>
<p class="help-text">
点击"添加IP地址"按钮在弹窗中输入有效的IP地址支持IPv4格式点击"添加"即可
</p>
</div>
<div class="help-item">
<h4 class="help-title">数量限制</h4>
<p class="help-text">
每个用户最多可添加10个IP地址到白名单中超出限制需要先删除不需要的IP地址
</p>
</div>
<div class="help-item">
<h4 class="help-title">安全提醒</h4>
<p class="help-text">
请确保只添加您信任的IP地址避免将API访问权限泄露给未授权的服务器
</p>
</div>
</div>
</el-card>
</div>
</template>
</ListPageLayout>
</template>
<script setup>
import { whiteListApi } from '@/api'
import FilterItem from '@/components/common/FilterItem.vue'
import FilterSection from '@/components/common/FilterSection.vue'
import ListPageLayout from '@/components/common/ListPageLayout.vue'
import { useCertification } from '@/composables/useCertification'
import {
CalendarIcon,
ComputerDesktopIcon,
PlusIcon,
QuestionMarkCircleIcon,
ShieldCheckIcon,
TrashIcon
} from '@heroicons/vue/24/outline'
import { ElMessage, ElMessageBox } from 'element-plus'
// 认证相关
const {
isCertified,
certificationLoading,
requiresCertification,
callProtectedAPI,
canCallAPI
} = useCertification()
// 响应式数据
const loading = ref(false)
const addLoading = ref(false)
const deleteLoading = ref('')
const whiteListData = ref([])
const addFormRef = ref()
const showAddForm = ref(false)
// 表单数据
const addForm = reactive({
ipAddress: ''
})
// 筛选条件
const filters = reactive({
keyword: '',
status: ''
})
// 表单验证规则
const addFormRules = {
ipAddress: [
{ required: true, message: '请输入IP地址', trigger: 'blur' },
{
pattern: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
message: '请输入有效的IPv4地址格式',
trigger: 'blur'
}
]
}
// 搜索防抖
let searchTimer = null
// 页面加载时获取数据
onMounted(() => {
loadWhiteList()
})
// 获取白名单列表
const loadWhiteList = async () => {
loading.value = true
try {
const response = await callProtectedAPI(whiteListApi.getWhiteList)
if (response) {
whiteListData.value = response.data.items || []
} else {
// 如果API调用被阻止显示空数据
whiteListData.value = []
}
} catch (error) {
console.error('获取白名单失败:', error)
if (canCallAPI.value) {
ElMessage.error('获取白名单失败')
}
} finally {
loading.value = false
}
}
// 添加IP地址
const handleAddIP = async () => {
if (!addFormRef.value) return
try {
await addFormRef.value.validate()
} catch (error) {
return
}
if (whiteListData.value.length >= 10) {
ElMessage.warning('白名单已满请先删除不需要的IP地址')
return
}
addLoading.value = true
try {
const response = await callProtectedAPI(whiteListApi.addWhiteListIP, addForm.ipAddress)
if (response) {
ElMessage.success('添加IP地址成功')
addForm.ipAddress = ''
showAddForm.value = false
await loadWhiteList()
}
} catch (error) {
console.error('添加IP地址失败:', error)
if (canCallAPI.value) {
ElMessage.error(error.response?.data?.message || '添加IP地址失败')
}
} finally {
addLoading.value = false
}
}
// 删除IP地址
const handleDeleteIP = async (ipAddress) => {
try {
await ElMessageBox.confirm(
`确定要删除IP地址 "${ipAddress}" 吗?`,
'确认删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
} catch {
return
}
deleteLoading.value = ipAddress
try {
const response = await callProtectedAPI(whiteListApi.deleteWhiteListIP, ipAddress)
if (response) {
ElMessage.success('删除IP地址成功')
await loadWhiteList()
}
} catch (error) {
console.error('删除IP地址失败:', error)
if (canCallAPI.value) {
ElMessage.error(error.response?.data?.message || '删除IP地址失败')
}
} finally {
deleteLoading.value = ''
}
}
// 处理筛选变化
const handleFilterChange = () => {
loadWhiteList()
}
// 处理搜索
const handleSearch = () => {
if (searchTimer) {
clearTimeout(searchTimer)
}
searchTimer = setTimeout(() => {
loadWhiteList()
}, 500)
}
// 重置筛选
const resetFilters = () => {
filters.keyword = ''
filters.status = ''
loadWhiteList()
}
// 格式化日期
const formatDate = (dateString) => {
if (!dateString) return '-'
const date = new Date(dateString)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
}
</script>
<style scoped>
.help-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.help-item {
padding: 16px;
border-left: 4px solid #409eff;
background-color: #f8f9fa;
border-radius: 4px;
}
.help-title {
font-weight: 600;
color: #303133;
margin-bottom: 8px;
}
.help-text {
color: #606266;
line-height: 1.6;
margin: 0;
}
/* 响应式设计 */
@media (max-width: 768px) {
.help-content {
grid-template-columns: 1fr;
}
}
</style>