|
|
|
|
@ -20,7 +20,7 @@
|
|
|
|
|
|
|
|
|
|
<div class="data-table-card">
|
|
|
|
|
<el-table
|
|
|
|
|
:data="paginatedBuildings"
|
|
|
|
|
:data="buildings"
|
|
|
|
|
stripe
|
|
|
|
|
@selection-change="handleBuildingSelect"
|
|
|
|
|
row-key="id"
|
|
|
|
|
@ -45,7 +45,7 @@
|
|
|
|
|
</el-table>
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="buildingPage"
|
|
|
|
|
:total="filteredBuildings.length"
|
|
|
|
|
:total="buildingTotal"
|
|
|
|
|
:page-size="buildingPageSize"
|
|
|
|
|
size="small"
|
|
|
|
|
background
|
|
|
|
|
@ -70,7 +70,7 @@
|
|
|
|
|
|
|
|
|
|
<div class="data-table-card">
|
|
|
|
|
<el-table
|
|
|
|
|
:data="paginatedRooms"
|
|
|
|
|
:data="filteredRooms"
|
|
|
|
|
stripe
|
|
|
|
|
@selection-change="handleRoomSelect"
|
|
|
|
|
row-key="id"
|
|
|
|
|
@ -96,12 +96,13 @@
|
|
|
|
|
</el-table>
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="roomPage"
|
|
|
|
|
:total="filteredRooms.length"
|
|
|
|
|
:total="roomTotal"
|
|
|
|
|
:page-size="roomPageSize"
|
|
|
|
|
size="small"
|
|
|
|
|
background
|
|
|
|
|
layout="total, prev, pager, next"
|
|
|
|
|
style="justify-content: flex-end; margin-top: 16px"
|
|
|
|
|
@current-change="loadRooms"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -120,10 +121,11 @@
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="data-table-card">
|
|
|
|
|
<el-table :data="paginatedCameras" stripe @selection-change="handleCameraSelect">
|
|
|
|
|
<el-table :data="cameras" stripe @selection-change="handleCameraSelect">
|
|
|
|
|
<el-table-column type="selection" width="45" />
|
|
|
|
|
<el-table-column prop="deviceNo" label="摄像头编号" min-width="180" />
|
|
|
|
|
<el-table-column prop="name" label="摄像头名称" min-width="180" />
|
|
|
|
|
<el-table-column prop="ip" label="IP地址" width="160" />
|
|
|
|
|
<el-table-column prop="streamUrl" label="流地址" width="160" />
|
|
|
|
|
<el-table-column prop="streamType" label="流类型" width="100" align="center" />
|
|
|
|
|
<el-table-column prop="position" label="安装位置" min-width="200" />
|
|
|
|
|
<el-table-column prop="status" label="状态" width="90" align="center">
|
|
|
|
|
@ -140,7 +142,7 @@
|
|
|
|
|
</el-table>
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="cameraPage"
|
|
|
|
|
:total="filteredCameras.length"
|
|
|
|
|
:total="cameraTotal"
|
|
|
|
|
:page-size="cameraPageSize"
|
|
|
|
|
size="small"
|
|
|
|
|
background
|
|
|
|
|
@ -200,18 +202,21 @@
|
|
|
|
|
<!-- 摄像头弹窗 -->
|
|
|
|
|
<el-dialog v-model="cameraDialogVisible" :title="cameraEditing ? '编辑摄像头' : '添加摄像头'" width="480px" align-center destroy-on-close>
|
|
|
|
|
<el-form :model="cameraForm" label-width="100px" label-position="left">
|
|
|
|
|
<el-form-item label="摄像头编号" required>
|
|
|
|
|
<el-input v-model="cameraForm.deviceNo" placeholder="请输入摄像头编号" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="摄像头名称" required>
|
|
|
|
|
<el-input v-model="cameraForm.name" placeholder="请输入摄像头名称" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="IP地址" required>
|
|
|
|
|
<el-input v-model="cameraForm.ip" placeholder="例如 192.168.1.100" />
|
|
|
|
|
<el-form-item label="流地址" required>
|
|
|
|
|
<el-input v-model="cameraForm.streamUrl" placeholder="例如 192.168.1.100" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="流类型">
|
|
|
|
|
<el-select v-model="cameraForm.streamType" placeholder="请选择流类型" style="width: 100%">
|
|
|
|
|
<el-option label="WebRTC" value="webrtc" />
|
|
|
|
|
<el-option label="RTSP" value="rtsp" />
|
|
|
|
|
<el-option label="RTMP" value="rtmp" />
|
|
|
|
|
<el-option label="USB" value="usb" />
|
|
|
|
|
<el-option label="webrtc" value="webrtc" />
|
|
|
|
|
<el-option label="rtsp" value="rtsp" />
|
|
|
|
|
<el-option label="rtmp" value="rtmp" />
|
|
|
|
|
<el-option label="usb" value="usb" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="安装位置">
|
|
|
|
|
@ -233,9 +238,10 @@
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, computed } from 'vue'
|
|
|
|
|
import { ref, computed, onMounted, watch } from 'vue'
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
import { Search, Plus, Delete, Edit } from '@element-plus/icons-vue'
|
|
|
|
|
import { getBuildings, addBuilding, updateBuilding, deleteBuilding, getRooms, addRoom, updateRoom, deleteRoom, getCameras, addCamera, updateCamera, deleteCamera } from '@/api/info'
|
|
|
|
|
|
|
|
|
|
// ========== 教学楼 ==========
|
|
|
|
|
const buildingPage = ref(1)
|
|
|
|
|
@ -246,26 +252,47 @@ const buildingEditing = ref(false)
|
|
|
|
|
const selectedBuildings = ref([])
|
|
|
|
|
const currentBuilding = ref(null)
|
|
|
|
|
|
|
|
|
|
const buildings = ref([
|
|
|
|
|
{ id: 1, name: '第一教学楼', floorCount: 5, roomCount: 30, status: '启用' },
|
|
|
|
|
{ id: 2, name: '第二教学楼', floorCount: 6, roomCount: 36, status: '启用' },
|
|
|
|
|
{ id: 3, name: '实验楼', floorCount: 4, roomCount: 20, status: '启用' }
|
|
|
|
|
])
|
|
|
|
|
const buildings = ref([])
|
|
|
|
|
const buildingTotal = ref(0)
|
|
|
|
|
|
|
|
|
|
const buildingForm = ref({ name: '', floorCount: 1, status: '启用' })
|
|
|
|
|
const loadBuildings = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await getBuildings({
|
|
|
|
|
current: buildingPage.value,
|
|
|
|
|
size: buildingPageSize.value,
|
|
|
|
|
keyword: buildingSearch.value || undefined
|
|
|
|
|
})
|
|
|
|
|
const pageData = res.data || {}
|
|
|
|
|
buildings.value = (pageData.records || []).map(b => ({
|
|
|
|
|
id: b.id,
|
|
|
|
|
name: b.buildingName,
|
|
|
|
|
floorCount: b.floors,
|
|
|
|
|
roomCount: b.classroomCount || 0,
|
|
|
|
|
status: b.status === 1 ? '启用' : '停用'
|
|
|
|
|
}))
|
|
|
|
|
buildingTotal.value = pageData.total || 0
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const filteredBuildings = computed(() => {
|
|
|
|
|
if (!buildingSearch.value) return buildings.value
|
|
|
|
|
return buildings.value.filter(b => b.name.includes(buildingSearch.value))
|
|
|
|
|
watch(buildingPage, () => loadBuildings())
|
|
|
|
|
watch(buildingSearch, () => {
|
|
|
|
|
if (buildingPage.value !== 1) {
|
|
|
|
|
buildingPage.value = 1
|
|
|
|
|
} else {
|
|
|
|
|
loadBuildings()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const paginatedBuildings = computed(() => {
|
|
|
|
|
const start = (buildingPage.value - 1) * buildingPageSize.value
|
|
|
|
|
return filteredBuildings.value.slice(start, start + buildingPageSize.value)
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
loadBuildings()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const buildingForm = ref({ name: '', floorCount: 1, status: '启用' })
|
|
|
|
|
|
|
|
|
|
const handleBuildingSelect = (rows) => { selectedBuildings.value = rows }
|
|
|
|
|
const handleBuildingRowClick = (row) => { currentBuilding.value = row; roomPage.value = 1; currentRoom.value = null }
|
|
|
|
|
const handleBuildingRowClick = (row) => { currentBuilding.value = row; roomPage.value = 1; currentRoom.value = null; loadRooms() }
|
|
|
|
|
|
|
|
|
|
const showBuildingDialog = (row) => {
|
|
|
|
|
if (row) {
|
|
|
|
|
@ -278,37 +305,55 @@ const showBuildingDialog = (row) => {
|
|
|
|
|
buildingDialogVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const saveBuilding = () => {
|
|
|
|
|
if (buildingEditing.value) {
|
|
|
|
|
const idx = buildings.value.findIndex(b => b.id === buildingForm.value.id)
|
|
|
|
|
if (idx > -1) buildings.value[idx] = { ...buildingForm.value }
|
|
|
|
|
ElMessage.success('编辑成功')
|
|
|
|
|
} else {
|
|
|
|
|
buildingForm.value.id = Date.now()
|
|
|
|
|
buildingForm.value.roomCount = 0
|
|
|
|
|
buildings.value.push({ ...buildingForm.value })
|
|
|
|
|
ElMessage.success('添加成功')
|
|
|
|
|
const saveBuilding = async () => {
|
|
|
|
|
const payload = {
|
|
|
|
|
buildingName: buildingForm.value.name,
|
|
|
|
|
floors: buildingForm.value.floorCount,
|
|
|
|
|
status: buildingForm.value.status === '启用' ? 1 : 0
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
if (buildingEditing.value) {
|
|
|
|
|
payload.id = buildingForm.value.id
|
|
|
|
|
await updateBuilding(payload)
|
|
|
|
|
ElMessage.success('编辑成功')
|
|
|
|
|
} else {
|
|
|
|
|
await addBuilding(payload)
|
|
|
|
|
ElMessage.success('添加成功')
|
|
|
|
|
}
|
|
|
|
|
buildingDialogVisible.value = false
|
|
|
|
|
await loadBuildings()
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
}
|
|
|
|
|
buildingDialogVisible.value = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const deleteBuildingRow = (row) => {
|
|
|
|
|
ElMessageBox.confirm(`确认删除教学楼 "${row.name}"?`, '提示', { type: 'warning' })
|
|
|
|
|
.then(() => {
|
|
|
|
|
buildings.value = buildings.value.filter(b => b.id !== row.id)
|
|
|
|
|
if (currentBuilding.value?.id === row.id) currentBuilding.value = null
|
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
.then(async () => {
|
|
|
|
|
try {
|
|
|
|
|
await deleteBuilding([row.id])
|
|
|
|
|
if (currentBuilding.value?.id === row.id) currentBuilding.value = null
|
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
await loadBuildings()
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const batchDeleteBuilding = () => {
|
|
|
|
|
ElMessageBox.confirm(`确认删除选中的 ${selectedBuildings.value.length} 个教学楼?`, '批量删除', { type: 'warning' })
|
|
|
|
|
.then(() => {
|
|
|
|
|
const ids = selectedBuildings.value.map(b => b.id)
|
|
|
|
|
buildings.value = buildings.value.filter(b => !ids.includes(b.id))
|
|
|
|
|
currentBuilding.value = null
|
|
|
|
|
ElMessage.success('批量删除成功')
|
|
|
|
|
.then(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const ids = selectedBuildings.value.map(b => b.id)
|
|
|
|
|
await deleteBuilding(ids)
|
|
|
|
|
currentBuilding.value = null
|
|
|
|
|
ElMessage.success('批量删除成功')
|
|
|
|
|
await loadBuildings()
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
@ -322,32 +367,42 @@ const roomEditing = ref(false)
|
|
|
|
|
const selectedRooms = ref([])
|
|
|
|
|
const currentRoom = ref(null)
|
|
|
|
|
|
|
|
|
|
const rooms = ref([
|
|
|
|
|
{ id: 1, buildingId: 1, roomNo: '101', roomName: '阶梯教室101', capacity: 200, cameraCount: 2, status: '启用' },
|
|
|
|
|
{ id: 2, buildingId: 1, roomNo: '102', roomName: '多媒体教室102', capacity: 100, cameraCount: 1, status: '启用' },
|
|
|
|
|
{ id: 3, buildingId: 1, roomNo: '201', roomName: '多媒体教室201', capacity: 120, cameraCount: 1, status: '启用' },
|
|
|
|
|
{ id: 4, buildingId: 1, roomNo: '301', roomName: '报告厅301', capacity: 300, cameraCount: 3, status: '启用' },
|
|
|
|
|
{ id: 5, buildingId: 2, roomNo: '101', roomName: '多媒体教室101', capacity: 100, cameraCount: 1, status: '启用' },
|
|
|
|
|
{ id: 6, buildingId: 2, roomNo: '102', roomName: '多媒体教室102', capacity: 100, cameraCount: 1, status: '停用' },
|
|
|
|
|
{ id: 7, buildingId: 3, roomNo: 'E101', roomName: '物理实验室', capacity: 60, cameraCount: 1, status: '启用' },
|
|
|
|
|
{ id: 8, buildingId: 3, roomNo: 'E201', roomName: '化学实验室', capacity: 50, cameraCount: 1, status: '启用' }
|
|
|
|
|
])
|
|
|
|
|
const rooms = ref([])
|
|
|
|
|
const roomTotal = ref(0)
|
|
|
|
|
|
|
|
|
|
const loadRooms = async () => {
|
|
|
|
|
if (!currentBuilding.value) return
|
|
|
|
|
try {
|
|
|
|
|
const res = await getRooms({
|
|
|
|
|
current: roomPage.value,
|
|
|
|
|
size: roomPageSize.value,
|
|
|
|
|
buildingId: currentBuilding.value.id
|
|
|
|
|
})
|
|
|
|
|
const pageData = res.data || {}
|
|
|
|
|
rooms.value = (pageData.records || []).map(r => ({
|
|
|
|
|
id: r.id,
|
|
|
|
|
buildingId: r.buildingId,
|
|
|
|
|
roomNo: r.roomNo,
|
|
|
|
|
roomName: r.roomName,
|
|
|
|
|
capacity: r.capacity,
|
|
|
|
|
cameraCount: r.deviceCount || 0,
|
|
|
|
|
status: r.status === 1 ? '启用' : '停用'
|
|
|
|
|
}))
|
|
|
|
|
roomTotal.value = pageData.total || 0
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const roomForm = ref({ roomNo: '', roomName: '', capacity: 100, status: '启用' })
|
|
|
|
|
|
|
|
|
|
const filteredRooms = computed(() => {
|
|
|
|
|
let list = rooms.value.filter(r => r.buildingId === currentBuilding.value?.id)
|
|
|
|
|
if (roomSearch.value) list = list.filter(r => r.roomNo.includes(roomSearch.value) || r.roomName.includes(roomSearch.value))
|
|
|
|
|
return list
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const paginatedRooms = computed(() => {
|
|
|
|
|
const start = (roomPage.value - 1) * roomPageSize.value
|
|
|
|
|
return filteredRooms.value.slice(start, start + roomPageSize.value)
|
|
|
|
|
if (!roomSearch.value) return rooms.value
|
|
|
|
|
return rooms.value.filter(r => r.roomNo.includes(roomSearch.value) || r.roomName.includes(roomSearch.value))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const handleRoomSelect = (rows) => { selectedRooms.value = rows }
|
|
|
|
|
const handleRoomRowClick = (row) => { currentRoom.value = row; cameraPage.value = 1 }
|
|
|
|
|
const handleRoomRowClick = (row) => { currentRoom.value = row; cameraPage.value = 1; loadCameras() }
|
|
|
|
|
|
|
|
|
|
const showRoomDialog = (row) => {
|
|
|
|
|
if (row) {
|
|
|
|
|
@ -360,45 +415,60 @@ const showRoomDialog = (row) => {
|
|
|
|
|
roomDialogVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const saveRoom = () => {
|
|
|
|
|
if (roomEditing.value) {
|
|
|
|
|
const idx = rooms.value.findIndex(r => r.id === roomForm.value.id)
|
|
|
|
|
if (idx > -1) rooms.value[idx] = { ...roomForm.value }
|
|
|
|
|
ElMessage.success('编辑成功')
|
|
|
|
|
} else {
|
|
|
|
|
roomForm.value.id = Date.now()
|
|
|
|
|
roomForm.value.buildingId = currentBuilding.value.id
|
|
|
|
|
roomForm.value.cameraCount = 0
|
|
|
|
|
rooms.value.push({ ...roomForm.value })
|
|
|
|
|
// 更新教学楼教室数量
|
|
|
|
|
const b = buildings.value.find(b => b.id === currentBuilding.value.id)
|
|
|
|
|
if (b) b.roomCount = (b.roomCount || 0) + 1
|
|
|
|
|
ElMessage.success('添加成功')
|
|
|
|
|
const saveRoom = async () => {
|
|
|
|
|
const payload = {
|
|
|
|
|
roomNo: roomForm.value.roomNo,
|
|
|
|
|
roomName: roomForm.value.roomName,
|
|
|
|
|
buildingId: currentBuilding.value.id,
|
|
|
|
|
capacity: roomForm.value.capacity,
|
|
|
|
|
status: roomForm.value.status === '启用' ? 1 : 0
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
if (roomEditing.value) {
|
|
|
|
|
payload.id = roomForm.value.id
|
|
|
|
|
await updateRoom(payload)
|
|
|
|
|
ElMessage.success('编辑成功')
|
|
|
|
|
} else {
|
|
|
|
|
await addRoom(payload)
|
|
|
|
|
ElMessage.success('添加成功')
|
|
|
|
|
}
|
|
|
|
|
roomDialogVisible.value = false
|
|
|
|
|
await loadRooms()
|
|
|
|
|
await loadBuildings()
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
}
|
|
|
|
|
roomDialogVisible.value = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const deleteRoomRow = (row) => {
|
|
|
|
|
ElMessageBox.confirm(`确认删除教室 "${row.roomName}"?`, '提示', { type: 'warning' })
|
|
|
|
|
.then(() => {
|
|
|
|
|
rooms.value = rooms.value.filter(r => r.id !== row.id)
|
|
|
|
|
const b = buildings.value.find(b => b.id === currentBuilding.value.id)
|
|
|
|
|
if (b && b.roomCount > 0) b.roomCount--
|
|
|
|
|
if (currentRoom.value?.id === row.id) currentRoom.value = null
|
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
.then(async () => {
|
|
|
|
|
try {
|
|
|
|
|
await deleteRoom([row.id])
|
|
|
|
|
if (currentRoom.value?.id === row.id) currentRoom.value = null
|
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
await loadRooms()
|
|
|
|
|
await loadBuildings()
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const batchDeleteRoom = () => {
|
|
|
|
|
ElMessageBox.confirm(`确认删除选中的 ${selectedRooms.value.length} 间教室?`, '批量删除', { type: 'warning' })
|
|
|
|
|
.then(() => {
|
|
|
|
|
const ids = selectedRooms.value.map(r => r.id)
|
|
|
|
|
rooms.value = rooms.value.filter(r => !ids.includes(r.id))
|
|
|
|
|
const b = buildings.value.find(b => b.id === currentBuilding.value.id)
|
|
|
|
|
if (b) b.roomCount = Math.max(0, b.roomCount - ids.length)
|
|
|
|
|
currentRoom.value = null
|
|
|
|
|
ElMessage.success('批量删除成功')
|
|
|
|
|
.then(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const ids = selectedRooms.value.map(r => r.id)
|
|
|
|
|
await deleteRoom(ids)
|
|
|
|
|
currentRoom.value = null
|
|
|
|
|
ElMessage.success('批量删除成功')
|
|
|
|
|
await loadRooms()
|
|
|
|
|
await loadBuildings()
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
@ -411,27 +481,43 @@ const cameraDialogVisible = ref(false)
|
|
|
|
|
const cameraEditing = ref(false)
|
|
|
|
|
const selectedCameras = ref([])
|
|
|
|
|
|
|
|
|
|
const cameras = ref([
|
|
|
|
|
{ id: 1, roomId: 1, name: '前置摄像头', ip: '192.168.1.101', streamType: 'webrtc', position: '黑板正上方', status: '在线' },
|
|
|
|
|
{ id: 2, roomId: 1, name: '后置摄像头', ip: '192.168.1.102', streamType: 'rtsp', position: '教室后方中央', status: '在线' },
|
|
|
|
|
{ id: 3, roomId: 2, name: '前置摄像头', ip: '192.168.1.103', streamType: 'webrtc', position: '黑板正上方', status: '在线' },
|
|
|
|
|
{ id: 4, roomId: 3, name: '前置摄像头', ip: '192.168.1.104', streamType: 'rtmp', position: '黑板正上方', status: '在线' },
|
|
|
|
|
{ id: 5, roomId: 4, name: '前置摄像头', ip: '192.168.1.105', streamType: 'webrtc', position: '讲台上方', status: '在线' },
|
|
|
|
|
{ id: 6, roomId: 4, name: '左侧摄像头', ip: '192.168.1.106', streamType: 'rtsp', position: '左侧墙壁', status: '在线' },
|
|
|
|
|
{ id: 7, roomId: 4, name: '右侧摄像头', ip: '192.168.1.107', streamType: 'usb', position: '右侧墙壁', status: '离线' }
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const cameraForm = ref({ name: '', ip: '', streamType: 'webrtc', position: '', status: '在线' })
|
|
|
|
|
|
|
|
|
|
const filteredCameras = computed(() => {
|
|
|
|
|
let list = cameras.value.filter(c => c.roomId === currentRoom.value?.id)
|
|
|
|
|
if (cameraSearch.value) list = list.filter(c => c.name.includes(cameraSearch.value) || c.ip.includes(cameraSearch.value))
|
|
|
|
|
return list
|
|
|
|
|
})
|
|
|
|
|
const cameras = ref([])
|
|
|
|
|
const cameraTotal = ref(0)
|
|
|
|
|
|
|
|
|
|
const loadCameras = async () => {
|
|
|
|
|
if (!currentRoom.value) return
|
|
|
|
|
try {
|
|
|
|
|
const res = await getCameras({
|
|
|
|
|
current: cameraPage.value,
|
|
|
|
|
size: cameraPageSize.value,
|
|
|
|
|
classroomId: currentRoom.value.id,
|
|
|
|
|
deviceName: cameraSearch.value || undefined
|
|
|
|
|
})
|
|
|
|
|
const pageData = res.data || {}
|
|
|
|
|
cameras.value = (pageData.records || []).map(c => ({
|
|
|
|
|
id: c.id,
|
|
|
|
|
deviceNo:c.deviceNo,
|
|
|
|
|
name: c.deviceName,
|
|
|
|
|
streamUrl: c.streamUrl,
|
|
|
|
|
streamType: c.streamType,
|
|
|
|
|
position: c.location,
|
|
|
|
|
status: c.onlineStatus === 1 ? '在线' : '离线'
|
|
|
|
|
}))
|
|
|
|
|
cameraTotal.value = pageData.total || 0
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const paginatedCameras = computed(() => {
|
|
|
|
|
const start = (cameraPage.value - 1) * cameraPageSize.value
|
|
|
|
|
return filteredCameras.value.slice(start, start + cameraPageSize.value)
|
|
|
|
|
const cameraForm = ref({ deviceNo: '', name: '', ip: '', streamType: 'webrtc', position: '', status: '在线' })
|
|
|
|
|
|
|
|
|
|
watch(cameraPage, () => loadCameras())
|
|
|
|
|
watch(cameraSearch, () => {
|
|
|
|
|
if (cameraPage.value !== 1) {
|
|
|
|
|
cameraPage.value = 1
|
|
|
|
|
} else {
|
|
|
|
|
loadCameras()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const handleCameraSelect = (rows) => { selectedCameras.value = rows }
|
|
|
|
|
@ -442,46 +528,66 @@ const showCameraDialog = (row) => {
|
|
|
|
|
cameraForm.value = { ...row }
|
|
|
|
|
} else {
|
|
|
|
|
cameraEditing.value = false
|
|
|
|
|
cameraForm.value = { name: '', ip: '', streamType: 'webrtc', position: '', status: '在线' }
|
|
|
|
|
cameraForm.value = { deviceNo: '', name: '', ip: '', streamType: 'webrtc', position: '', status: '在线' }
|
|
|
|
|
}
|
|
|
|
|
cameraDialogVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const saveCamera = () => {
|
|
|
|
|
if (cameraEditing.value) {
|
|
|
|
|
const idx = cameras.value.findIndex(c => c.id === cameraForm.value.id)
|
|
|
|
|
if (idx > -1) cameras.value[idx] = { ...cameraForm.value }
|
|
|
|
|
ElMessage.success('编辑成功')
|
|
|
|
|
} else {
|
|
|
|
|
cameraForm.value.id = Date.now()
|
|
|
|
|
cameraForm.value.roomId = currentRoom.value.id
|
|
|
|
|
cameras.value.push({ ...cameraForm.value })
|
|
|
|
|
const r = rooms.value.find(r => r.id === currentRoom.value.id)
|
|
|
|
|
if (r) r.cameraCount = (r.cameraCount || 0) + 1
|
|
|
|
|
ElMessage.success('添加成功')
|
|
|
|
|
const saveCamera = async () => {
|
|
|
|
|
const payload = {
|
|
|
|
|
deviceNo: cameraForm.value.deviceNo,
|
|
|
|
|
deviceType: 'camera',
|
|
|
|
|
deviceName: cameraForm.value.name,
|
|
|
|
|
streamType: cameraForm.value.streamType,
|
|
|
|
|
streamUrl: cameraForm.value.ip,
|
|
|
|
|
location: cameraForm.value.position,
|
|
|
|
|
onlineStatus: cameraForm.value.status === '在线' ? 1 : 0,
|
|
|
|
|
classroomId: currentRoom.value.id
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
if (cameraEditing.value) {
|
|
|
|
|
payload.id = cameraForm.value.id
|
|
|
|
|
await updateCamera(payload)
|
|
|
|
|
ElMessage.success('编辑成功')
|
|
|
|
|
} else {
|
|
|
|
|
await addCamera(payload)
|
|
|
|
|
ElMessage.success('添加成功')
|
|
|
|
|
}
|
|
|
|
|
cameraDialogVisible.value = false
|
|
|
|
|
await loadCameras()
|
|
|
|
|
await loadRooms()
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
}
|
|
|
|
|
cameraDialogVisible.value = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const deleteCameraRow = (row) => {
|
|
|
|
|
ElMessageBox.confirm(`确认删除摄像头 "${row.name}"?`, '提示', { type: 'warning' })
|
|
|
|
|
.then(() => {
|
|
|
|
|
cameras.value = cameras.value.filter(c => c.id !== row.id)
|
|
|
|
|
const r = rooms.value.find(r => r.id === currentRoom.value.id)
|
|
|
|
|
if (r && r.cameraCount > 0) r.cameraCount--
|
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
.then(async () => {
|
|
|
|
|
try {
|
|
|
|
|
await deleteCamera([row.id])
|
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
await loadCameras()
|
|
|
|
|
await loadRooms()
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const batchDeleteCamera = () => {
|
|
|
|
|
ElMessageBox.confirm(`确认删除选中的 ${selectedCameras.value.length} 个摄像头?`, '批量删除', { type: 'warning' })
|
|
|
|
|
.then(() => {
|
|
|
|
|
const ids = selectedCameras.value.map(c => c.id)
|
|
|
|
|
cameras.value = cameras.value.filter(c => !ids.includes(c.id))
|
|
|
|
|
const r = rooms.value.find(r => r.id === currentRoom.value.id)
|
|
|
|
|
if (r) r.cameraCount = Math.max(0, r.cameraCount - ids.length)
|
|
|
|
|
ElMessage.success('批量删除成功')
|
|
|
|
|
.then(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const ids = selectedCameras.value.map(c => c.id)
|
|
|
|
|
await deleteCamera(ids)
|
|
|
|
|
ElMessage.success('批量删除成功')
|
|
|
|
|
await loadCameras()
|
|
|
|
|
await loadRooms()
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
|