|
|
|
|
@ -0,0 +1,514 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="page-container fade-in-up">
|
|
|
|
|
<div class="page-header">
|
|
|
|
|
<h2 class="page-title">教学楼信息</h2>
|
|
|
|
|
<p class="page-subtitle">管理教学楼、教室及摄像头设备信息</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 教学楼区域 -->
|
|
|
|
|
<div class="section-card">
|
|
|
|
|
<div class="section-title">
|
|
|
|
|
<el-icon :size="18"><OfficeBuilding /></el-icon>
|
|
|
|
|
<span>教学楼列表</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="filter-bar">
|
|
|
|
|
<el-input v-model="buildingSearch" placeholder="搜索教学楼名称..." :prefix-icon="Search" clearable size="default" style="width: 240px" />
|
|
|
|
|
<el-button type="primary" :icon="Plus" @click="showBuildingDialog()">添加教学楼</el-button>
|
|
|
|
|
<el-button :icon="Delete" :disabled="selectedBuildings.length === 0" @click="batchDeleteBuilding">批量删除</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="data-table-card">
|
|
|
|
|
<el-table
|
|
|
|
|
:data="paginatedBuildings"
|
|
|
|
|
stripe
|
|
|
|
|
@selection-change="handleBuildingSelect"
|
|
|
|
|
row-key="id"
|
|
|
|
|
highlight-current-row
|
|
|
|
|
@current-change="handleBuildingRowClick"
|
|
|
|
|
>
|
|
|
|
|
<el-table-column type="selection" width="45" />
|
|
|
|
|
<el-table-column prop="name" label="教学楼名称" min-width="220" />
|
|
|
|
|
<el-table-column prop="floorCount" label="楼层数" width="100" align="center" />
|
|
|
|
|
<el-table-column prop="roomCount" label="教室数量" width="100" align="center" />
|
|
|
|
|
<el-table-column prop="status" label="状态" width="90" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="row.status === '启用' ? 'success' : 'info'" size="small">{{ row.status }}</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="操作" width="160" align="center" fixed="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button link type="primary" size="small" :icon="Edit" @click.stop="showBuildingDialog(row)">编辑</el-button>
|
|
|
|
|
<el-button link type="danger" size="small" :icon="Delete" @click.stop="deleteBuildingRow(row)">删除</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="buildingPage"
|
|
|
|
|
:total="filteredBuildings.length"
|
|
|
|
|
:page-size="buildingPageSize"
|
|
|
|
|
size="small"
|
|
|
|
|
background
|
|
|
|
|
layout="total, prev, pager, next"
|
|
|
|
|
style="justify-content: flex-end; margin-top: 16px"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 教室区域 -->
|
|
|
|
|
<div class="section-card" v-if="currentBuilding">
|
|
|
|
|
<div class="section-title">
|
|
|
|
|
<el-icon :size="18"><HomeFilled /></el-icon>
|
|
|
|
|
<span>{{ currentBuilding.name }} — 教室列表</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="filter-bar">
|
|
|
|
|
<el-input v-model="roomSearch" placeholder="搜索教室编号..." :prefix-icon="Search" clearable size="default" style="width: 240px" />
|
|
|
|
|
<el-button type="primary" :icon="Plus" @click="showRoomDialog()">添加教室</el-button>
|
|
|
|
|
<el-button :icon="Delete" :disabled="selectedRooms.length === 0" @click="batchDeleteRoom">批量删除</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="data-table-card">
|
|
|
|
|
<el-table
|
|
|
|
|
:data="paginatedRooms"
|
|
|
|
|
stripe
|
|
|
|
|
@selection-change="handleRoomSelect"
|
|
|
|
|
row-key="id"
|
|
|
|
|
highlight-current-row
|
|
|
|
|
@current-change="handleRoomRowClick"
|
|
|
|
|
>
|
|
|
|
|
<el-table-column type="selection" width="45" />
|
|
|
|
|
<el-table-column prop="roomNo" label="教室编号" width="140" />
|
|
|
|
|
<el-table-column prop="roomName" label="教室名称" min-width="220" />
|
|
|
|
|
<el-table-column prop="capacity" label="容纳人数" width="100" align="center" />
|
|
|
|
|
<el-table-column prop="cameraCount" label="摄像头数量" width="110" align="center" />
|
|
|
|
|
<el-table-column prop="status" label="状态" width="90" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="row.status === '启用' ? 'success' : 'info'" size="small">{{ row.status }}</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="操作" width="160" align="center" fixed="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button link type="primary" size="small" :icon="Edit" @click.stop="showRoomDialog(row)">编辑</el-button>
|
|
|
|
|
<el-button link type="danger" size="small" :icon="Delete" @click.stop="deleteRoomRow(row)">删除</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="roomPage"
|
|
|
|
|
:total="filteredRooms.length"
|
|
|
|
|
:page-size="roomPageSize"
|
|
|
|
|
size="small"
|
|
|
|
|
background
|
|
|
|
|
layout="total, prev, pager, next"
|
|
|
|
|
style="justify-content: flex-end; margin-top: 16px"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 摄像头区域 -->
|
|
|
|
|
<div class="section-card" v-if="currentRoom">
|
|
|
|
|
<div class="section-title">
|
|
|
|
|
<el-icon :size="18"><Camera /></el-icon>
|
|
|
|
|
<span>{{ currentBuilding.name }} / {{ currentRoom.roomName }} — 摄像头列表</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="filter-bar">
|
|
|
|
|
<el-input v-model="cameraSearch" placeholder="搜索摄像头名称..." :prefix-icon="Search" clearable size="default" style="width: 240px" />
|
|
|
|
|
<el-button type="primary" :icon="Plus" @click="showCameraDialog()">添加摄像头</el-button>
|
|
|
|
|
<el-button :icon="Delete" :disabled="selectedCameras.length === 0" @click="batchDeleteCamera">批量删除</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="data-table-card">
|
|
|
|
|
<el-table :data="paginatedCameras" stripe @selection-change="handleCameraSelect">
|
|
|
|
|
<el-table-column type="selection" width="45" />
|
|
|
|
|
<el-table-column prop="name" label="摄像头名称" min-width="180" />
|
|
|
|
|
<el-table-column prop="ip" label="IP地址" 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">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="row.status === '在线' ? 'success' : 'danger'" size="small">{{ row.status }}</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="操作" width="160" align="center" fixed="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button link type="primary" size="small" :icon="Edit" @click.stop="showCameraDialog(row)">编辑</el-button>
|
|
|
|
|
<el-button link type="danger" size="small" :icon="Delete" @click.stop="deleteCameraRow(row)">删除</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="cameraPage"
|
|
|
|
|
:total="filteredCameras.length"
|
|
|
|
|
:page-size="cameraPageSize"
|
|
|
|
|
size="small"
|
|
|
|
|
background
|
|
|
|
|
layout="total, prev, pager, next"
|
|
|
|
|
style="justify-content: flex-end; margin-top: 16px"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 教学楼弹窗 -->
|
|
|
|
|
<el-dialog v-model="buildingDialogVisible" :title="buildingEditing ? '编辑教学楼' : '添加教学楼'" width="480px" align-center destroy-on-close>
|
|
|
|
|
<el-form :model="buildingForm" label-width="100px" label-position="left">
|
|
|
|
|
<el-form-item label="教学楼名称" required>
|
|
|
|
|
<el-input v-model="buildingForm.name" placeholder="请输入教学楼名称" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="楼层数" required>
|
|
|
|
|
<el-input-number v-model="buildingForm.floorCount" :min="1" :max="20" style="width: 100%" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="状态">
|
|
|
|
|
<el-radio-group v-model="buildingForm.status">
|
|
|
|
|
<el-radio value="启用">启用</el-radio>
|
|
|
|
|
<el-radio value="停用">停用</el-radio>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<el-button @click="buildingDialogVisible = false">取消</el-button>
|
|
|
|
|
<el-button type="primary" @click="saveBuilding">保存</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<!-- 教室弹窗 -->
|
|
|
|
|
<el-dialog v-model="roomDialogVisible" :title="roomEditing ? '编辑教室' : '添加教室'" width="480px" align-center destroy-on-close>
|
|
|
|
|
<el-form :model="roomForm" label-width="100px" label-position="left">
|
|
|
|
|
<el-form-item label="教室编号" required>
|
|
|
|
|
<el-input v-model="roomForm.roomNo" placeholder="请输入教室编号" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="教室名称" required>
|
|
|
|
|
<el-input v-model="roomForm.roomName" placeholder="请输入教室名称" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="容纳人数" required>
|
|
|
|
|
<el-input-number v-model="roomForm.capacity" :min="10" :max="500" style="width: 100%" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="状态">
|
|
|
|
|
<el-radio-group v-model="roomForm.status">
|
|
|
|
|
<el-radio value="启用">启用</el-radio>
|
|
|
|
|
<el-radio value="停用">停用</el-radio>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<el-button @click="roomDialogVisible = false">取消</el-button>
|
|
|
|
|
<el-button type="primary" @click="saveRoom">保存</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<!-- 摄像头弹窗 -->
|
|
|
|
|
<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.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>
|
|
|
|
|
<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-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="安装位置">
|
|
|
|
|
<el-input v-model="cameraForm.position" placeholder="请输入安装位置描述" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="状态">
|
|
|
|
|
<el-radio-group v-model="cameraForm.status">
|
|
|
|
|
<el-radio value="在线">在线</el-radio>
|
|
|
|
|
<el-radio value="离线">离线</el-radio>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<el-button @click="cameraDialogVisible = false">取消</el-button>
|
|
|
|
|
<el-button type="primary" @click="saveCamera">保存</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, computed } from 'vue'
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
import { Search, Plus, Delete, Edit } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
|
|
|
// ========== 教学楼 ==========
|
|
|
|
|
const buildingPage = ref(1)
|
|
|
|
|
const buildingPageSize = ref(8)
|
|
|
|
|
const buildingSearch = ref('')
|
|
|
|
|
const buildingDialogVisible = ref(false)
|
|
|
|
|
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 buildingForm = ref({ name: '', floorCount: 1, status: '启用' })
|
|
|
|
|
|
|
|
|
|
const filteredBuildings = computed(() => {
|
|
|
|
|
if (!buildingSearch.value) return buildings.value
|
|
|
|
|
return buildings.value.filter(b => b.name.includes(buildingSearch.value))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const paginatedBuildings = computed(() => {
|
|
|
|
|
const start = (buildingPage.value - 1) * buildingPageSize.value
|
|
|
|
|
return filteredBuildings.value.slice(start, start + buildingPageSize.value)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const handleBuildingSelect = (rows) => { selectedBuildings.value = rows }
|
|
|
|
|
const handleBuildingRowClick = (row) => { currentBuilding.value = row; roomPage.value = 1; currentRoom.value = null }
|
|
|
|
|
|
|
|
|
|
const showBuildingDialog = (row) => {
|
|
|
|
|
if (row) {
|
|
|
|
|
buildingEditing.value = true
|
|
|
|
|
buildingForm.value = { ...row }
|
|
|
|
|
} else {
|
|
|
|
|
buildingEditing.value = false
|
|
|
|
|
buildingForm.value = { name: '', floorCount: 1, status: '启用' }
|
|
|
|
|
}
|
|
|
|
|
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('添加成功')
|
|
|
|
|
}
|
|
|
|
|
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('删除成功')
|
|
|
|
|
})
|
|
|
|
|
.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('批量删除成功')
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========== 教室 ==========
|
|
|
|
|
const roomPage = ref(1)
|
|
|
|
|
const roomPageSize = ref(8)
|
|
|
|
|
const roomSearch = ref('')
|
|
|
|
|
const roomDialogVisible = ref(false)
|
|
|
|
|
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 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)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const handleRoomSelect = (rows) => { selectedRooms.value = rows }
|
|
|
|
|
const handleRoomRowClick = (row) => { currentRoom.value = row; cameraPage.value = 1 }
|
|
|
|
|
|
|
|
|
|
const showRoomDialog = (row) => {
|
|
|
|
|
if (row) {
|
|
|
|
|
roomEditing.value = true
|
|
|
|
|
roomForm.value = { ...row }
|
|
|
|
|
} else {
|
|
|
|
|
roomEditing.value = false
|
|
|
|
|
roomForm.value = { roomNo: '', roomName: '', capacity: 100, status: '启用' }
|
|
|
|
|
}
|
|
|
|
|
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('添加成功')
|
|
|
|
|
}
|
|
|
|
|
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('删除成功')
|
|
|
|
|
})
|
|
|
|
|
.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('批量删除成功')
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========== 摄像头 ==========
|
|
|
|
|
const cameraPage = ref(1)
|
|
|
|
|
const cameraPageSize = ref(8)
|
|
|
|
|
const cameraSearch = ref('')
|
|
|
|
|
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 paginatedCameras = computed(() => {
|
|
|
|
|
const start = (cameraPage.value - 1) * cameraPageSize.value
|
|
|
|
|
return filteredCameras.value.slice(start, start + cameraPageSize.value)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const handleCameraSelect = (rows) => { selectedCameras.value = rows }
|
|
|
|
|
|
|
|
|
|
const showCameraDialog = (row) => {
|
|
|
|
|
if (row) {
|
|
|
|
|
cameraEditing.value = true
|
|
|
|
|
cameraForm.value = { ...row }
|
|
|
|
|
} else {
|
|
|
|
|
cameraEditing.value = false
|
|
|
|
|
cameraForm.value = { 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('添加成功')
|
|
|
|
|
}
|
|
|
|
|
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('删除成功')
|
|
|
|
|
})
|
|
|
|
|
.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('批量删除成功')
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.section-card {
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #262626;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
padding-bottom: 12px;
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.data-table-card {
|
|
|
|
|
margin-top: 0;
|
|
|
|
|
}
|
|
|
|
|
</style>
|