255 lines
8.4 KiB
Vue

<template>
<div class="os-devices-page">
<ElCard shadow="never">
<template #header>
<div class="card-header">
<span>系统添加</span>
<div class="header-actions">
<ElButton type="primary" @click="showScanDialog = true">
<ElIcon><Search /></ElIcon>
扫描网络
</ElButton>
</div>
</div>
</template>
<!-- 扫描结果列表 -->
<div v-if="scanResults.length > 0" class="scan-results">
<div class="results-header">
<span>发现 {{ scanResults.length }} 台设备,请选择要添加的设备:</span>
<div>
<ElButton size="small" @click="selectAll">全选</ElButton>
<ElButton size="small" @click="deselectAll">取消全选</ElButton>
</div>
</div>
<ElTable :data="scanResults" @selection-change="handleSelectionChange" ref="tableRef">
<ElTableColumn type="selection" width="55" />
<ElTableColumn prop="ipAddress" label="IP 地址" width="150" />
<ElTableColumn label="操作系统" width="120">
<template #default="{ row }">
<ElTag :type="getOsTagType(row.osType)" size="small">{{ row.osType }}</ElTag>
</template>
</ElTableColumn>
<ElTableColumn prop="hostname" label="主机名" width="150">
<template #default="{ row }">
{{ row.hostname || '-' }}
</template>
</ElTableColumn>
<ElTableColumn label="状态" width="100">
<template #default="{ row }">
<ElTag :type="row.isOnline ? 'success' : 'danger'" size="small">
{{ row.isOnline ? '在线' : '离线' }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn prop="discoveredAt" label="发现时间">
<template #default="{ row }">
{{ formatTime(row.discoveredAt) }}
</template>
</ElTableColumn>
</ElTable>
<div class="results-footer">
<span>已选择 {{ selectedDevices.length }} 台设备</span>
<div>
<ElButton @click="clearResults">取消</ElButton>
<ElButton type="primary" @click="saveSelectedDevices" :loading="saving" :disabled="selectedDevices.length === 0">
添加选中设备
</ElButton>
</div>
</div>
</div>
<!-- 空状态 -->
<ElEmpty v-else description="点击扫描网络发现局域网内的设备" />
</ElCard>
<!-- 扫描对话框 -->
<ElDialog v-model="showScanDialog" title="扫描网络" width="500px" :close-on-click-modal="false">
<ElForm :model="scanForm" label-width="100px">
<ElFormItem label="网段">
<ElInput v-model="scanForm.networkSegment" placeholder="例如: 192.168.1.0" />
</ElFormItem>
<ElFormItem label="子网掩码">
<ElSelect v-model="scanForm.subnetMask" style="width: 100%">
<ElOption label="/24 (255.255.255.0) - 254 台主机" value="/24" />
<ElOption label="/23 (255.255.254.0) - 510 台主机" value="/23" />
<ElOption label="/22 (255.255.252.0) - 1022 台主机" value="/22" />
</ElSelect>
</ElFormItem>
</ElForm>
<div v-if="scanning" class="scan-progress">
<ElProgress :percentage="scanProgress.progressPercentage" :format="formatProgress" />
<p>当前扫描: {{ scanProgress.currentIp || '-' }}</p>
<p>已发现: {{ scanProgress.foundDevices }} 台设备</p>
</div>
<template #footer>
<ElButton @click="cancelScan" v-if="scanning">取消扫描</ElButton>
<ElButton @click="showScanDialog = false" v-else>关闭</ElButton>
<ElButton type="primary" @click="startScan" :loading="scanning" :disabled="scanning">
{{ scanning ? '扫描中...' : '开始扫描' }}
</ElButton>
</template>
</ElDialog>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
import { osDeviceApi } from '@/api/amt'
defineOptions({ name: 'OsDevices' })
const tableRef = ref()
const scanResults = ref<any[]>([])
const selectedDevices = ref<any[]>([])
const saving = ref(false)
// 扫描相关
const showScanDialog = ref(false)
const scanning = ref(false)
const scanForm = ref({ networkSegment: '192.168.1.0', subnetMask: '/24' })
const scanProgress = ref({ scannedCount: 0, totalCount: 0, foundDevices: 0, progressPercentage: 0, currentIp: '' })
let currentTaskId = ''
const getOsTagType = (osType: string) => {
switch (osType) {
case 'Windows': return 'primary'
case 'Linux': return 'success'
default: return 'info'
}
}
const formatTime = (time: string) => {
if (!time) return '-'
return new Date(time).toLocaleString()
}
const formatProgress = () => {
return scanProgress.value.scannedCount + '/' + scanProgress.value.totalCount
}
const handleSelectionChange = (selection: any[]) => {
selectedDevices.value = selection
}
const selectAll = () => {
tableRef.value?.toggleAllSelection()
}
const deselectAll = () => {
tableRef.value?.clearSelection()
}
const startScan = async () => {
scanning.value = true
scanProgress.value = { scannedCount: 0, totalCount: 0, foundDevices: 0, progressPercentage: 0, currentIp: '' }
scanResults.value = []
selectedDevices.value = []
try {
const result = await osDeviceApi.startScan(scanForm.value.networkSegment, scanForm.value.subnetMask)
currentTaskId = result.taskId
// 轮询扫描进度
const pollProgress = async () => {
if (!scanning.value) return
try {
const progress = await osDeviceApi.getScanStatus(currentTaskId)
// -1 表示任务不存在,停止轮询
if (progress.progressPercentage === -1) {
scanning.value = false
ElMessage.warning('扫描任务已失效')
return
}
scanProgress.value = progress
if (progress.progressPercentage < 100) {
setTimeout(pollProgress, 500)
} else {
// 扫描完成,获取结果
scanning.value = false
showScanDialog.value = false
await loadScanResults()
}
} catch (error) {
scanning.value = false
ElMessage.error('获取扫描进度失败')
}
}
setTimeout(pollProgress, 500)
} catch (error) {
scanning.value = false
ElMessage.error('启动扫描失败')
}
}
const cancelScan = async () => {
try {
await osDeviceApi.cancelScan(currentTaskId)
scanning.value = false
ElMessage.info('扫描已取消')
} catch (error) {
ElMessage.error('取消扫描失败')
}
}
const loadScanResults = async () => {
try {
const results = await osDeviceApi.getScanResults(currentTaskId)
scanResults.value = results
if (results.length === 0) {
ElMessage.info('未发现任何设备')
} else {
ElMessage.success(`发现 ${results.length} 台设备,请选择要添加的设备`)
}
} catch (error) {
ElMessage.error('获取扫描结果失败')
}
}
const saveSelectedDevices = async () => {
if (selectedDevices.value.length === 0) {
ElMessage.warning('请选择要添加的设备')
return
}
saving.value = true
try {
const selectedIps = selectedDevices.value.map(d => d.ipAddress)
const result = await osDeviceApi.saveSelectedDevices(currentTaskId, selectedIps)
ElMessage.success(result.message || `成功添加 ${result.savedCount} 台设备`)
clearResults()
} catch (error) {
ElMessage.error('添加设备失败')
} finally {
saving.value = false
}
}
const clearResults = () => {
scanResults.value = []
selectedDevices.value = []
currentTaskId = ''
}
</script>
<style scoped>
.os-devices-page { padding: 0; }
.card-header { display: flex; justify-content: space-between; align-items: center; font-size: 16px; font-weight: 500; }
.header-actions { display: flex; gap: 10px; }
.scan-results { }
.results-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
.results-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; padding-top: 15px; border-top: 1px solid #eee; }
.scan-progress { margin-top: 20px; text-align: center; }
.scan-progress p { margin: 10px 0; color: #666; }
</style>