<script setup lang="ts">
|
import { onMounted, reactive, ref } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { useRouter } from 'vue-router'
|
import { changeDeviceCollectorStatus, deleteDeviceCollectors, getDeviceCollectorList } from '@/api/deviceCollectors'
|
import type { CollectorOnlineStatus, CollectorStatus, DeviceCollectorRecord } from '@/stores/deviceCollectors'
|
import DeviceRelatedTests from '@/components/DeviceRelatedTests.vue'
|
|
interface FilterState {
|
device: string
|
dateRange: [string, string] | null
|
onlineStatus: string
|
status: string
|
}
|
|
const router = useRouter()
|
const onlineStatuses: CollectorOnlineStatus[] = ['在线', '离线']
|
const statuses: CollectorStatus[] = ['启用', '禁用']
|
const filters = reactive<FilterState>({ device: '', dateRange: null, onlineStatus: '', status: '' })
|
const records = ref<DeviceCollectorRecord[]>([])
|
const total = ref(0)
|
const currentPage = ref(1)
|
const pageSize = ref(10)
|
const loading = ref(false)
|
const submittingId = ref<number | null>(null)
|
|
const onlineStatusValues: Record<CollectorOnlineStatus, string> = { '离线': '0', '在线': '1' }
|
const statusValues: Record<CollectorStatus, string> = { '启用': '0', '禁用': '1' }
|
|
async function loadCollectors(showResult = false) {
|
loading.value = true
|
try {
|
const result = await getDeviceCollectorList({
|
pageNum: currentPage.value,
|
pageSize: pageSize.value,
|
device: filters.device.trim() || undefined,
|
beginCreateTime: filters.dateRange?.[0],
|
endCreateTime: filters.dateRange?.[1],
|
onlineStatus: filters.onlineStatus && filters.onlineStatus !== '全部'
|
? onlineStatusValues[filters.onlineStatus as CollectorOnlineStatus]
|
: undefined,
|
status: filters.status && filters.status !== '全部'
|
? statusValues[filters.status as CollectorStatus]
|
: undefined,
|
})
|
records.value = result.rows
|
total.value = result.total
|
if (showResult) ElMessage.success(`已找到 ${result.total} 个采集器`)
|
} catch (error) {
|
records.value = []
|
total.value = 0
|
ElMessage.error(error instanceof Error ? error.message : '采集器列表加载失败')
|
} finally {
|
loading.value = false
|
}
|
}
|
|
function applySearch() {
|
currentPage.value = 1
|
loadCollectors(true)
|
}
|
|
function resetSearch() {
|
Object.assign(filters, { device: '', dateRange: null, onlineStatus: '', status: '' })
|
currentPage.value = 1
|
loadCollectors()
|
}
|
|
function handlePageChange(page: number) {
|
currentPage.value = page
|
loadCollectors()
|
}
|
|
function handleSizeChange(size: number) {
|
pageSize.value = size
|
currentPage.value = 1
|
loadCollectors()
|
}
|
|
function createCollector() {
|
router.push('/device-collector/new')
|
}
|
|
function editCollector(item: DeviceCollectorRecord) {
|
router.push(`/device-collector/${item.id}/edit`)
|
}
|
|
async function changeStatus(item: DeviceCollectorRecord) {
|
const nextStatus: CollectorStatus = item.status === '启用' ? '禁用' : '启用'
|
try {
|
await ElMessageBox.confirm(
|
`确认${nextStatus}采集器“${item.name}”吗?`,
|
`${nextStatus}确认`,
|
{ type: 'warning', confirmButtonText: '确认', cancelButtonText: '取消' },
|
)
|
submittingId.value = item.id
|
await changeDeviceCollectorStatus(item.id, nextStatus)
|
ElMessage.success(`采集器已${nextStatus}`)
|
await loadCollectors()
|
} catch (error) {
|
if (error !== 'cancel' && error !== 'close') {
|
ElMessage.error(error instanceof Error ? error.message : `${nextStatus}采集器失败`)
|
}
|
} finally {
|
submittingId.value = null
|
}
|
}
|
|
async function deleteCollector(item: DeviceCollectorRecord) {
|
try {
|
await ElMessageBox.confirm(
|
`删除后无法恢复,确认删除采集器“${item.name}”吗?`,
|
'删除确认',
|
{ type: 'warning', confirmButtonText: '确认删除', cancelButtonText: '取消' },
|
)
|
submittingId.value = item.id
|
await deleteDeviceCollectors(item.id)
|
if (records.value.length === 1 && currentPage.value > 1) currentPage.value -= 1
|
ElMessage.success('采集器已删除')
|
await loadCollectors()
|
} catch (error) {
|
if (error !== 'cancel' && error !== 'close') {
|
ElMessage.error(error instanceof Error ? error.message : '删除采集器失败')
|
}
|
} finally {
|
submittingId.value = null
|
}
|
}
|
|
onMounted(loadCollectors)
|
</script>
|
|
<template>
|
<div class="collector-page">
|
<section class="filter-panel" aria-label="采集器查询条件">
|
<header class="section-heading"><div><i></i><strong>查询条件</strong><span>FILTER CONDITIONS</span></div><small>共 {{ total }} 个采集器</small></header>
|
<div class="filter-grid">
|
<label><span>设备</span><el-input v-model="filters.device" clearable placeholder="输入IP、设备编码、MAC或名称" @keyup.enter="applySearch" /></label>
|
<label class="date-filter"><span>创建时间</span><el-date-picker v-model="filters.dateRange" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" range-separator="至" unlink-panels /></label>
|
<label><span>在线状态</span><el-select v-model="filters.onlineStatus" clearable placeholder="全部状态"><el-option label="全部" value="全部" /><el-option v-for="item in onlineStatuses" :key="item" :label="item" :value="item" /></el-select></label>
|
<label><span>状态</span><el-select v-model="filters.status" clearable placeholder="全部状态"><el-option label="全部" value="全部" /><el-option v-for="item in statuses" :key="item" :label="item" :value="item" /></el-select></label>
|
<div class="filter-actions"><button class="primary-button" type="button" :disabled="loading" @click="applySearch"><el-icon><Search /></el-icon>查询</button><button class="secondary-button" type="button" :disabled="loading" @click="resetSearch"><el-icon><RefreshLeft /></el-icon>重置</button></div>
|
</div>
|
</section>
|
|
<section class="result-panel">
|
<div class="result-toolbar"><div><i></i><strong>采集器列表</strong><span>{{ total }} RESULTS · 按创建时间倒序</span></div><button class="primary-button" type="button" @click="createCollector"><el-icon><Plus /></el-icon>手动添加</button></div>
|
<div v-loading="loading" class="table-wrap">
|
<table>
|
<thead><tr><th>设备编码</th><th>IP</th><th>MAC</th><th>名称</th><th>制造商</th><th>型号</th><th>通道数</th><th>备注</th><th>在线状态</th><th>关联测试</th><th class="action-column">操作</th></tr></thead>
|
<tbody>
|
<tr v-for="item in records" :key="item.id">
|
<td class="code-cell">{{ item.deviceCode }}</td><td class="code-cell">{{ item.ip }}</td><td class="code-cell">{{ item.mac }}</td><td><strong>{{ item.name }}</strong></td><td>{{ item.manufacturer }}</td><td>{{ item.model }}</td><td>{{ item.channelCount }}</td><td :title="item.remark">{{ item.remark || '-' }}</td>
|
<td><span class="online-state" :class="item.onlineStatus === '在线' ? 'online' : 'offline'"><i></i>{{ item.onlineStatus }}</span></td>
|
<td><DeviceRelatedTests :tests="item.relatedTests" /></td>
|
<td class="action-column"><div class="row-actions"><button type="button" :disabled="submittingId !== null" :class="item.status === '启用' ? 'disable-action' : 'enable-action'" @click="changeStatus(item)">{{ submittingId === item.id ? '处理中' : item.status === '启用' ? '禁用' : '启用' }}</button><button type="button" :disabled="submittingId !== null" @click="editCollector(item)">编辑</button><button class="delete-action" type="button" :disabled="submittingId !== null" @click="deleteCollector(item)">删除</button></div></td>
|
</tr>
|
<tr v-if="!loading && !records.length"><td colspan="11" class="empty-cell">暂无符合条件的采集器</td></tr>
|
</tbody>
|
</table>
|
</div>
|
<footer class="pagination-bar"><span>显示 {{ records.length }} 条,共 {{ total }} 条</span><el-pagination :current-page="currentPage" :page-size="pageSize" :page-sizes="[10, 20, 50]" :total="total" layout="sizes, prev, pager, next" background @current-change="handlePageChange" @size-change="handleSizeChange" /></footer>
|
</section>
|
</div>
|
</template>
|
|
<style scoped>
|
.collector-page { width: 100%; }
|
.primary-button, .secondary-button { height: 34px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 0 13px; border: 1px solid transparent; border-radius: 3px; font-size: 13px; font-weight: 500; cursor: pointer; white-space: nowrap; }
|
.primary-button { color: #fff; background: var(--blue); border-color: var(--blue); }
|
.primary-button:hover { background: #176fcb; border-color: #176fcb; }
|
.secondary-button { color: #52606d; background: #fff; border-color: var(--line-strong); }
|
.secondary-button:hover { color: var(--blue); border-color: #9dc8f5; }
|
.filter-panel, .result-panel { background: #fff; border: 1px solid #d8e1e9; }
|
.filter-panel { margin-bottom: 16px; }
|
.section-heading, .result-toolbar { display: flex; align-items: center; justify-content: space-between; background: var(--surface-soft); border-bottom: 1px solid var(--line); }
|
.section-heading { height: 44px; padding: 0 16px; }
|
.section-heading > div, .result-toolbar > div { display: flex; align-items: center; gap: 8px; }
|
.section-heading > div > i, .result-toolbar > div:first-child > i { width: 3px; height: 16px; background: #2b78a3; }
|
.section-heading strong, .result-toolbar strong { color: var(--ink); font-size: 15px; font-weight: 600; }
|
.section-heading span, .result-toolbar span { color: #81909d; font-size: 10px; }
|
.section-heading small { color: #647380; font-size: 11px; }
|
.filter-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px 18px; padding: 16px; }
|
.filter-grid label { width: 100%; min-width: 0; display: grid; grid-template-columns: 72px minmax(0, 1fr); align-items: center; gap: 8px; }
|
.filter-grid label > span { color: #52606d; font-size: 12px; font-weight: 500; }
|
.filter-grid :deep(.el-select), .filter-grid :deep(.el-date-editor) { width: 100%; }
|
.filter-grid :deep(.el-input__wrapper), .filter-grid :deep(.el-select__wrapper) { min-height: 34px; border-radius: 3px; box-shadow: 0 0 0 1px #d8e1e9 inset; background: #f8fafc; }
|
.filter-actions { grid-column: -2 / -1; min-width: 0; display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
|
.result-toolbar { height: 50px; padding: 0 16px; }
|
.table-wrap { overflow-x: auto; }
|
table { width: 100%; min-width: 1480px; table-layout: fixed; border-collapse: collapse; color: #52606d; font-size: 13px; }
|
th, td { height: 50px; padding: 0 12px; border-right: 1px solid #dde7f0; border-bottom: 1px solid #dde7f0; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
th { height: 40px; color: #52606d; background: #eaf4ff; border-color: #c7e0ff; font-size: 12px; font-weight: 600; }
|
th:nth-child(1) { width: 115px; } th:nth-child(2) { width: 120px; } th:nth-child(3) { width: 130px; } th:nth-child(4) { width: 130px; }
|
th:nth-child(5), th:nth-child(6) { width: 100px; } th:nth-child(7) { width: 80px; } th:nth-child(8) { width: 145px; } th:nth-child(9) { width: 100px; } th:nth-child(10) { width: 180px; } th:last-child { width: 180px; }
|
th:last-child, td:last-child { border-right: 0; text-align: center; }
|
.action-column { position: sticky; right: 0; z-index: 2; background: #fff; overflow: visible; border-left: 0; box-shadow: inset 1px 0 0 #b4c7d9, -8px 0 14px rgba(23, 49, 74, .12); }
|
th.action-column { z-index: 3; background: #eaf4ff; }
|
tbody tr:hover td.action-column { background: #f7fbff; }
|
tbody tr:hover { background: #f7fbff; }
|
tbody tr:last-child td { border-bottom: 0; }
|
td strong { color: #26333f; font-weight: 600; }
|
.code-cell { color: #647380; font-family: 'Cascadia Mono', Consolas, monospace; font-size: 12px; }
|
.online-state { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; }
|
.online-state i { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
|
.online-state.online { color: #23865e; }
|
.online-state.offline { color: #a64a4a; }
|
.row-actions { display: flex; align-items: center; justify-content: center; gap: 12px; }
|
.row-actions button { border: 0; color: var(--blue); background: transparent; font-size: 12px; font-weight: 500; cursor: pointer; }
|
.row-actions .disable-action, .row-actions .delete-action { color: #ad4a4a; }
|
.row-actions .enable-action { color: #23865e; }
|
.empty-cell { height: 160px; color: #8795a3; text-align: center; }
|
.pagination-bar { min-height: 58px; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 8px 14px 8px 16px; border-top: 1px solid var(--line); }
|
.pagination-bar > span { color: #7b8794; font-size: 11px; }
|
.pagination-bar :deep(.el-pagination) { --el-pagination-button-bg-color: #edf4fa; --el-pagination-hover-color: var(--blue); }
|
@media (max-width: 1320px) { .filter-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
@media (max-width: 980px) { .filter-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
@media (max-width: 680px) {
|
.filter-grid { grid-template-columns: minmax(0, 1fr); gap: 12px; padding: 14px 12px; }
|
.filter-actions { justify-content: stretch; }
|
.filter-actions button { flex: 1; }
|
.section-heading, .result-toolbar { padding-left: 12px; }
|
.section-heading span, .result-toolbar span { display: none; }
|
.pagination-bar { align-items: flex-start; flex-direction: column; overflow-x: auto; }
|
}
|
</style>
|