feat(alerts): 添加微信告警、WebSocket实时推送和地图功能
- 新增微信告警服务(wechat_service)和告警任务(alert_tasks) - 新增 WebSocket 实时推送端点 - 新增监控管理模块(monitor) - 增强统计仪表板:趋势图、区域分布、光功率历史 - 设备管理:添加坐标信息、标签系统、复合索引优化 - 前端:重构Dashboard/Charts页面,新增业务组件 - 新增5个数据库迁移(坐标、复合索引、标签、光功率历史、显示名) - 更新部署配置和脚本 - 新增测试框架基础结构
This commit is contained in:
@@ -16,6 +16,13 @@
|
||||
<el-button v-if="can('device.import')" type="success" size="small" @click="importDialogVisible = true">
|
||||
数据导入
|
||||
</el-button>
|
||||
<a href="/api/devices/export/csv" class="csv-export-btn" title="导出CSV">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||
<polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
|
||||
</svg>
|
||||
导出CSV
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,6 +56,12 @@
|
||||
<el-option label="未知" value="unknown" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">标签</label>
|
||||
<el-select v-model="filters.tag" placeholder="全部" clearable @change="search" size="small" style="width:130px">
|
||||
<el-option v-for="t in availableTags" :key="t" :label="t" :value="t" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">搜索</label>
|
||||
<el-input
|
||||
@@ -428,6 +441,7 @@
|
||||
<el-input v-model="editForm.place_type" placeholder="如:宿舍、教室、办公室…" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="editForm.notes" type="textarea" :rows="2" /></el-form-item>
|
||||
<el-form-item label="标签"><el-input v-model="editForm.tags" placeholder="多个标签用逗号分隔,如:重点设备,考试用" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="editVisible = false">取消</el-button>
|
||||
@@ -632,7 +646,8 @@ const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const regions = ref([])
|
||||
const filters = ref({ region: '', keyword: '', school_name: '', status: '' })
|
||||
const filters = ref({ region: '', keyword: '', school_name: '', status: '', tag: '' })
|
||||
const availableTags = ref([])
|
||||
|
||||
const detailVisible = ref(false)
|
||||
const selectedDevice = ref({})
|
||||
@@ -647,7 +662,7 @@ let cooldownTimer = null
|
||||
const clearing = ref(false)
|
||||
|
||||
const editVisible = ref(false)
|
||||
const editForm = ref({ region: '', school_name: '', building: '', room_number: '', place_type: '', notes: '' })
|
||||
const editForm = ref({ region: '', school_name: '', building: '', room_number: '', place_type: '', notes: '', tags: '' })
|
||||
const regionOptions = ref([])
|
||||
const editSaving = ref(false)
|
||||
|
||||
@@ -1003,6 +1018,7 @@ const openEdit = () => {
|
||||
room_number: selectedDevice.value.room_number || '',
|
||||
place_type: selectedDevice.value.place_type || '',
|
||||
notes: selectedDevice.value.notes || '',
|
||||
tags: selectedDevice.value.tags || '',
|
||||
}
|
||||
loadRegionOptions()
|
||||
detailVisible.value = false
|
||||
@@ -1063,6 +1079,7 @@ const loadDevices = async () => {
|
||||
keyword: filters.value.keyword || undefined,
|
||||
school_name: filters.value.school_name || undefined,
|
||||
status: filters.value.status || undefined,
|
||||
tag: filters.value.tag || undefined,
|
||||
})
|
||||
devices.value = data.items
|
||||
total.value = data.total
|
||||
@@ -1079,12 +1096,20 @@ const handleSizeChange = (val) => {
|
||||
loadDevices()
|
||||
}
|
||||
|
||||
const fetchTags = async () => {
|
||||
try {
|
||||
const { data } = await request.get('/devices/tags')
|
||||
availableTags.value = data || []
|
||||
} catch {}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.school_name) {
|
||||
filters.value.keyword = route.query.school_name
|
||||
}
|
||||
loadRegions()
|
||||
loadDevices()
|
||||
fetchTags()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1123,6 +1148,26 @@ onMounted(() => {
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.csv-export-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 5px 12px;
|
||||
background: var(--bg-elevated);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-family: var(--font-sans);
|
||||
text-decoration: none;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.csv-export-btn:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* 筛选栏 */
|
||||
|
||||
Reference in New Issue
Block a user