Files
dashboard/frontend/src/views/share-code/log.vue
T
2026-06-18 17:54:59 +08:00

353 lines
8.9 KiB
Vue

<template>
<div class="share-code-log-container">
<el-card class="search-card">
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item label="MAC">
<el-input
v-model="searchForm.mac_addr"
placeholder="AA:BB:CC:DD:EE:FF"
clearable
style="width: 210px"
/>
</el-form-item>
<el-form-item label="分享码">
<el-input
v-model="searchForm.share_code"
placeholder="5 位"
clearable
maxlength="5"
style="width: 140px"
/>
</el-form-item>
<el-form-item label="动作">
<el-select
v-model="searchForm.action"
placeholder="全部"
clearable
style="width: 120px"
>
<el-option label="导出" value="export" />
<el-option label="导入" value="import" />
</el-select>
</el-form-item>
<el-form-item label="IP">
<el-input
v-model="searchForm.ip_addr"
placeholder="IPv4/IPv6"
clearable
style="width: 180px"
/>
</el-form-item>
<el-form-item label="时间">
<el-date-picker
v-model="searchForm.timeRange"
type="datetimerange"
start-placeholder="开始"
end-placeholder="结束"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DDTHH:mm:ss"
:default-time="defaultTime"
style="width: 360px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="table-card">
<div class="card-header">
<span class="title">分享日志</span>
</div>
<div class="table-loading-host" v-loading="loading">
<el-table
:data="tableData"
border
stripe
style="width: 100%"
>
<el-table-column prop="id" label="ID" min-width="70" />
<el-table-column prop="mac_addr" label="MAC" min-width="160" show-overflow-tooltip />
<el-table-column prop="share_code" label="分享码" min-width="90" />
<el-table-column prop="action" label="动作" min-width="90">
<template #default="{ row }">
<el-tag v-if="row.action === 'export'" type="success" size="small">导出</el-tag>
<el-tag v-else-if="row.action === 'import'" type="warning" size="small">导入</el-tag>
<span v-else>{{ row.action || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="ip_addr" label="IP" min-width="150" show-overflow-tooltip />
<el-table-column prop="expire_at" label="到期时间" min-width="170">
<template #default="{ row }">
{{ formatDateTime(row.expire_at) }}
</template>
</el-table-column>
<el-table-column prop="create_at" label="操作时间" min-width="170">
<template #default="{ row }">
{{ formatDateTime(row.create_at) }}
</template>
</el-table-column>
<el-table-column label="EQ 快照" min-width="110">
<template #default="{ row }">
<el-button type="primary" size="small" @click="handleViewEq(row)">查看</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination">
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
</el-card>
<el-dialog
v-model="eqDialogVisible"
:title="eqDialogTitle"
width="780px"
class="eq-dialog"
@closed="resetEqDialog"
>
<div v-if="eqDialogData === null">
<el-empty description="暂无数据" :image-size="80" />
</div>
<div v-else class="eq-dialog-body">
<VueJsonPretty
:data="eqDialogData"
:deep="3"
:show-length="true"
:show-line-number="false"
:show-icon="true"
:show-select-controller="false"
:editable="false"
/>
</div>
<template #footer>
<el-button @click="eqDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { Search, Refresh } from '@element-plus/icons-vue'
import VueJsonPretty from 'vue-json-pretty'
import 'vue-json-pretty/lib/styles.css'
import { getShareCodeLogs } from '@/api/shareCodeLog'
const loading = ref(false)
const tableData = ref([])
const pagination = reactive({
page: 1,
pageSize: 50,
total: 0
})
const defaultTime = [
new Date(2000, 1, 1, 0, 0, 0),
new Date(2000, 1, 1, 23, 59, 59)
]
const searchForm = reactive({
mac_addr: '',
share_code: '',
action: undefined,
ip_addr: '',
timeRange: null
})
const eqDialogVisible = ref(false)
const eqDialogTitle = ref('EQ 快照')
const eqDialogData = ref(null)
function resetEqDialog() {
eqDialogTitle.value = 'EQ 快照'
eqDialogData.value = null
}
function safeParseJson(value) {
if (value === null || value === undefined) return null
if (typeof value === 'object') return value
if (typeof value === 'string') {
try {
return JSON.parse(value)
} catch {
return { _raw: value }
}
}
return { value }
}
function handleViewEq(row) {
eqDialogTitle.value = `${row.mac_addr || ''} · ${row.share_code || ''}`.trim()
eqDialogData.value = safeParseJson(row.eq_data)
eqDialogVisible.value = true
}
function formatDateTime(value) {
if (!value) return '-'
const date = new Date(value)
if (Number.isNaN(date.getTime())) return value
const pad = (n) => String(n).padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
}
function buildQueryParams() {
const params = {
skip: (pagination.page - 1) * pagination.pageSize,
limit: pagination.pageSize,
mac_addr: searchForm.mac_addr || undefined,
share_code: searchForm.share_code || undefined,
action: searchForm.action || undefined,
ip_addr: searchForm.ip_addr || undefined,
sort_by: 'id',
sort_order: 'desc'
}
if (Array.isArray(searchForm.timeRange) && searchForm.timeRange.length === 2) {
params.start_at = searchForm.timeRange[0] || undefined
params.end_at = searchForm.timeRange[1] || undefined
}
return params
}
async function loadData() {
loading.value = true
try {
const res = await getShareCodeLogs(buildQueryParams())
if (res.code === 1 && res.data) {
tableData.value = res.data.items || []
pagination.total = res.data.total || 0
} else if (res.code === 2) {
tableData.value = []
pagination.total = 0
}
} catch (e) {
console.error(e)
ElMessage.error('加载失败')
} finally {
loading.value = false
}
}
function handleSearch() {
pagination.page = 1
loadData()
}
function handleReset() {
searchForm.mac_addr = ''
searchForm.share_code = ''
searchForm.action = undefined
searchForm.ip_addr = ''
searchForm.timeRange = null
pagination.page = 1
loadData()
}
function handleSizeChange() {
loadData()
}
function handlePageChange() {
loadData()
}
onMounted(() => {
loadData()
})
</script>
<style scoped>
.share-code-log-container {
height: 100%;
}
.search-form {
display: flex;
flex-wrap: wrap;
}
.table-card {
min-height: 520px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.title {
font-size: 18px;
font-weight: 600;
color: #e2e8f0;
}
.pagination {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.eq-dialog-body {
max-height: 64vh;
overflow: auto;
}
.eq-dialog :deep(.vjs-tree) {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 12px;
line-height: 1.6;
color: #cbd5e1;
background: transparent;
}
.eq-dialog :deep(.vjs-tree-node.is-highlight),
.eq-dialog :deep(.vjs-tree-node:hover) {
background-color: rgba(51, 65, 85, 0.72);
border-radius: 4px;
}
.eq-dialog :deep(.vjs-key) {
color: #7dd3fc;
}
.eq-dialog :deep(.vjs-value-string) {
color: #86efac;
}
.eq-dialog :deep(.vjs-value-number) {
color: #fcd34d;
}
.eq-dialog :deep(.vjs-value-boolean) {
color: #f9a8d4;
}
.eq-dialog :deep(.vjs-value-null) {
color: #94a3b8;
}
</style>