| | |
| | | <template> |
| | | <div></div> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="门禁名称"> |
| | | <el-input |
| | | v-model="filters.name" |
| | | style="width: 240px" |
| | | placeholder="请输入门禁名称" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @change="getTableData" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="门禁状态"> |
| | | <el-select |
| | | v-model="filters.status" |
| | | style="width: 240px" |
| | | placeholder="请选择门禁状态" |
| | | clearable |
| | | @change="getTableData" |
| | | > |
| | | <el-option label="正常" value="1"></el-option> |
| | | <el-option label="异常" value="0"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="区域"> |
| | | <el-input |
| | | v-model="filters.area" |
| | | style="width: 240px" |
| | | placeholder="请输入区域" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @change="getTableData" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">搜索</el-button> |
| | | <el-button @click="resetFilters">重置</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus"> 新增 </el-button> |
| | | <el-button type="danger" icon="Delete" :disabled="multipleList.length <= 0" @click="batchDelete">批量删除</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | </PIMTable> |
| | | </div> |
| | | <!-- 新增编辑弹窗 --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px"> |
| | | <el-form :model="formData" label-width="100px"> |
| | | <el-form-item label="门禁名称" required> |
| | | <el-input v-model="formData.name" placeholder="请输入门禁名称"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="区域" required> |
| | | <el-input v-model="formData.area" placeholder="请输入区域"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="位置" required> |
| | | <el-input v-model="formData.location" placeholder="请输入具体位置"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="状态" required> |
| | | <el-select v-model="formData.status" placeholder="请选择状态"> |
| | | <el-option label="正常" value="1"></el-option> |
| | | <el-option label="异常" value="0"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="描述"> |
| | | <el-input v-model="formData.description" type="textarea" rows="3" placeholder="请输入描述信息"></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="saveData">确定</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | <script> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from 'vue'; |
| | | import { Search, Plus, Delete } from '@element-plus/icons-vue'; |
| | | import { ElMessage, ElMessageBox } from 'element-plus'; |
| | | import dayjs from 'dayjs'; |
| | | |
| | | // 定义假数据 |
| | | const mockData = [ |
| | | { id: 1, name: '东门门禁', area: '生产区', location: '东门入口', status: '1', lastUpdate: '2025-12-30 08:30:00', description: '主要用于员工上下班通行' }, |
| | | { id: 2, name: '西门门禁', area: '仓储区', location: '西门入口', status: '1', lastUpdate: '2025-12-30 09:15:00', description: '主要用于物流车辆通行' }, |
| | | { id: 3, name: '南门门禁', area: '办公区', location: '南门入口', status: '0', lastUpdate: '2025-12-30 10:20:00', description: '主要用于访客通行,当前故障' }, |
| | | { id: 4, name: '北门门禁', area: '生产区', location: '北门入口', status: '1', lastUpdate: '2025-12-30 11:45:00', description: '主要用于原材料运输车辆通行' }, |
| | | { id: 5, name: '中控室门禁', area: '中控区', location: '中控室门口', status: '1', lastUpdate: '2025-12-30 13:20:00', description: '仅限授权人员进入' }, |
| | | ]; |
| | | |
| | | // 响应式数据 |
| | | const filters = reactive({ |
| | | name: '', |
| | | status: '', |
| | | area: '' |
| | | }); |
| | | |
| | | const dataList = ref([]); |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0 |
| | | }); |
| | | |
| | | const multipleList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref('新增门禁'); |
| | | const formData = reactive({ |
| | | id: '', |
| | | name: '', |
| | | area: '', |
| | | location: '', |
| | | status: '1', |
| | | description: '' |
| | | }); |
| | | |
| | | // 表格列配置 |
| | | const columns = [ |
| | | { |
| | | label: '门禁名称', |
| | | align: 'center', |
| | | prop: 'name', |
| | | }, |
| | | { |
| | | label: '区域', |
| | | align: 'center', |
| | | prop: 'area', |
| | | }, |
| | | { |
| | | label: '位置', |
| | | align: 'center', |
| | | prop: 'location', |
| | | }, |
| | | { |
| | | label: '状态', |
| | | align: 'center', |
| | | prop: 'status', |
| | | formatter: (row) => { |
| | | return row.status === '1' ? '<el-tag type="success">正常</el-tag>' : '<el-tag type="danger">异常</el-tag>'; |
| | | } |
| | | }, |
| | | { |
| | | label: '最后更新', |
| | | align: 'center', |
| | | prop: 'lastUpdate', |
| | | }, |
| | | { |
| | | label: '描述', |
| | | align: 'center', |
| | | prop: 'description', |
| | | }, |
| | | { |
| | | dataType: 'action', |
| | | label: '操作', |
| | | align: 'center', |
| | | fixed: 'right', |
| | | width: 140, |
| | | operation: [ |
| | | { |
| | | name: '编辑', |
| | | type: 'text', |
| | | clickFun: (row) => { |
| | | edit(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: '删除', |
| | | type: 'text', |
| | | clickFun: (row) => { |
| | | deleteRow(row.id); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | | |
| | | // 过滤后的数据 |
| | | const filteredData = computed(() => { |
| | | return mockData.filter(item => { |
| | | const nameMatch = !filters.name || item.name.includes(filters.name); |
| | | const statusMatch = !filters.status || item.status === filters.status; |
| | | const areaMatch = !filters.area || item.area.includes(filters.area); |
| | | return nameMatch && statusMatch && areaMatch; |
| | | }); |
| | | }); |
| | | |
| | | // 获取表格数据 |
| | | const getTableData = () => { |
| | | pagination.total = filteredData.value.length; |
| | | const start = (pagination.currentPage - 1) * pagination.pageSize; |
| | | const end = start + pagination.pageSize; |
| | | dataList.value = filteredData.value.slice(start, end); |
| | | }; |
| | | |
| | | // 重置过滤器 |
| | | const resetFilters = () => { |
| | | filters.name = ''; |
| | | filters.status = ''; |
| | | filters.area = ''; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | // 分页变化 |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | // 多选处理 |
| | | const handleSelectionChange = (selectionList) => { |
| | | multipleList.value = selectionList; |
| | | }; |
| | | |
| | | // 新增 |
| | | const add = () => { |
| | | dialogTitle.value = '新增门禁'; |
| | | formData.id = ''; |
| | | formData.name = ''; |
| | | formData.area = ''; |
| | | formData.location = ''; |
| | | formData.status = '1'; |
| | | formData.description = ''; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // 编辑 |
| | | const edit = (row) => { |
| | | dialogTitle.value = '编辑门禁'; |
| | | formData.id = row.id; |
| | | formData.name = row.name; |
| | | formData.area = row.area; |
| | | formData.location = row.location; |
| | | formData.status = row.status; |
| | | formData.description = row.description; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // 保存数据 |
| | | const saveData = () => { |
| | | if (!formData.name || !formData.area || !formData.location) { |
| | | ElMessage.warning('请填写必填字段'); |
| | | return; |
| | | } |
| | | |
| | | const currentTime = dayjs().format('YYYY-MM-DD HH:mm:ss'); |
| | | if (formData.id) { |
| | | // 编辑 |
| | | const index = mockData.findIndex(item => item.id === formData.id); |
| | | if (index !== -1) { |
| | | mockData[index] = { |
| | | ...formData, |
| | | lastUpdate: currentTime |
| | | }; |
| | | ElMessage.success('编辑成功'); |
| | | } |
| | | } else { |
| | | // 新增 |
| | | const newId = Math.max(...mockData.map(item => item.id), 0) + 1; |
| | | mockData.unshift({ |
| | | ...formData, |
| | | id: newId, |
| | | lastUpdate: currentTime |
| | | }); |
| | | ElMessage.success('新增成功'); |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | }; |
| | | |
| | | // 删除 |
| | | const deleteRow = (id) => { |
| | | ElMessageBox.confirm('此操作将永久删除该门禁记录, 是否继续?', '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning', |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === id); |
| | | if (index !== -1) { |
| | | mockData.splice(index, 1); |
| | | ElMessage.success('删除成功'); |
| | | getTableData(); |
| | | } |
| | | }).catch(() => {}); |
| | | }; |
| | | |
| | | // 批量删除 |
| | | const batchDelete = () => { |
| | | if (multipleList.value.length === 0) { |
| | | ElMessage.warning('请选择要删除的数据'); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm('此操作将永久删除所选门禁记录, 是否继续?', '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning', |
| | | }).then(() => { |
| | | const ids = multipleList.value.map(item => item.id); |
| | | mockData.forEach((item, index) => { |
| | | if (ids.includes(item.id)) { |
| | | mockData.splice(index, 1); |
| | | } |
| | | }); |
| | | ElMessage.success('删除成功'); |
| | | getTableData(); |
| | | multipleList.value = []; |
| | | }).catch(() => {}); |
| | | }; |
| | | |
| | | // 初始化数据 |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | <style> |
| | | |
| | | <style lang="scss" scoped> |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div></div> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="巡检名称"> |
| | | <el-input |
| | | v-model="filters.name" |
| | | style="width: 240px" |
| | | placeholder="请输入巡检名称" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @change="getTableData" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="巡检状态"> |
| | | <el-select |
| | | v-model="filters.status" |
| | | style="width: 240px" |
| | | placeholder="请选择巡检状态" |
| | | clearable |
| | | @change="getTableData" |
| | | > |
| | | <el-option label="待执行" value="0"></el-option> |
| | | <el-option label="执行中" value="1"></el-option> |
| | | <el-option label="已完成" value="2"></el-option> |
| | | <el-option label="已取消" value="3"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="巡检类型"> |
| | | <el-select |
| | | v-model="filters.type" |
| | | style="width: 240px" |
| | | placeholder="请选择巡检类型" |
| | | clearable |
| | | @change="getTableData" |
| | | > |
| | | <el-option label="定期巡检" value="0"></el-option> |
| | | <el-option label="临时巡检" value="1"></el-option> |
| | | <el-option label="故障巡检" value="2"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">搜索</el-button> |
| | | <el-button @click="resetFilters">重置</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus"> 新增 </el-button> |
| | | <el-button |
| | | type="danger" |
| | | icon="Delete" |
| | | :disabled="multipleList.length <= 0" |
| | | @click="batchDelete" |
| | | >批量删除</el-button |
| | | > |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | </PIMTable> |
| | | </div> |
| | | <!-- 新增编辑弹窗 --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px"> |
| | | <el-form :model="formData" label-width="100px"> |
| | | <el-form-item label="巡检名称" required> |
| | | <el-input |
| | | v-model="formData.name" |
| | | placeholder="请输入巡检名称" |
| | | ></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="巡检类型" required> |
| | | <el-select v-model="formData.type" placeholder="请选择巡检类型"> |
| | | <el-option label="定期巡检" value="0"></el-option> |
| | | <el-option label="临时巡检" value="1"></el-option> |
| | | <el-option label="故障巡检" value="2"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="巡检区域" required> |
| | | <el-input |
| | | v-model="formData.area" |
| | | placeholder="请输入巡检区域" |
| | | ></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="巡检人员" required> |
| | | <el-input |
| | | v-model="formData.inspector" |
| | | placeholder="请输入巡检人员" |
| | | ></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="计划开始时间" required> |
| | | <el-date-picker |
| | | v-model="formData.planStartTime" |
| | | type="datetime" |
| | | placeholder="请选择计划开始时间" |
| | | style="width: 100%" |
| | | ></el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item label="计划结束时间" required> |
| | | <el-date-picker |
| | | v-model="formData.planEndTime" |
| | | type="datetime" |
| | | placeholder="请选择计划结束时间" |
| | | style="width: 100%" |
| | | ></el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item label="巡检内容"> |
| | | <el-input |
| | | v-model="formData.content" |
| | | type="textarea" |
| | | rows="3" |
| | | placeholder="请输入巡检内容" |
| | | ></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="saveData">确定</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | <script> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from "vue"; |
| | | import { Search, Plus, Delete } from "@element-plus/icons-vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // 定义假数据 |
| | | const mockData = [ |
| | | { |
| | | id: 1, |
| | | name: "东门区域日常巡检", |
| | | type: "0", |
| | | status: "2", |
| | | planStartTime: "2025-12-30 08:00:00", |
| | | planEndTime: "2025-12-30 09:00:00", |
| | | actualStartTime: "2025-12-30 08:05:00", |
| | | actualEndTime: "2025-12-30 08:55:00", |
| | | inspector: "张三", |
| | | area: "东门区域", |
| | | content: "检查门禁设备运行状态、环境监测设备数据", |
| | | }, |
| | | { |
| | | id: 2, |
| | | name: "西门区域临时巡检", |
| | | type: "1", |
| | | status: "2", |
| | | planStartTime: "2025-12-30 10:00:00", |
| | | planEndTime: "2025-12-30 11:00:00", |
| | | actualStartTime: "2025-12-30 10:00:00", |
| | | actualEndTime: "2025-12-30 10:45:00", |
| | | inspector: "李四", |
| | | area: "西门区域", |
| | | content: "检查异常报警设备", |
| | | }, |
| | | { |
| | | id: 3, |
| | | name: "南门门禁故障巡检", |
| | | type: "2", |
| | | status: "1", |
| | | planStartTime: "2025-12-30 13:00:00", |
| | | planEndTime: "2025-12-30 14:00:00", |
| | | actualStartTime: "2025-12-30 13:10:00", |
| | | actualEndTime: "", |
| | | inspector: "王五", |
| | | area: "南门区域", |
| | | content: "修复门禁故障", |
| | | }, |
| | | { |
| | | id: 4, |
| | | name: "中控室定期巡检", |
| | | type: "0", |
| | | status: "0", |
| | | planStartTime: "2025-12-31 09:00:00", |
| | | planEndTime: "2025-12-31 10:00:00", |
| | | actualStartTime: "", |
| | | actualEndTime: "", |
| | | inspector: "赵六", |
| | | area: "中控区域", |
| | | content: "检查监控设备、服务器运行状态", |
| | | }, |
| | | { |
| | | id: 5, |
| | | name: "北门区域日常巡检", |
| | | type: "0", |
| | | status: "2", |
| | | planStartTime: "2025-12-30 15:00:00", |
| | | planEndTime: "2025-12-30 16:00:00", |
| | | actualStartTime: "2025-12-30 15:00:00", |
| | | actualEndTime: "2025-12-30 15:50:00", |
| | | inspector: "张三", |
| | | area: "北门区域", |
| | | content: "检查车辆识别设备、道闸运行状态", |
| | | }, |
| | | ]; |
| | | |
| | | // 响应式数据 |
| | | const filters = reactive({ |
| | | name: "", |
| | | status: "", |
| | | type: "", |
| | | }); |
| | | |
| | | const dataList = ref([]); |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const multipleList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref("新增巡检"); |
| | | const formData = reactive({ |
| | | id: "", |
| | | name: "", |
| | | type: "0", |
| | | status: "0", |
| | | planStartTime: "", |
| | | planEndTime: "", |
| | | actualStartTime: "", |
| | | actualEndTime: "", |
| | | inspector: "", |
| | | area: "", |
| | | content: "", |
| | | }); |
| | | |
| | | // 状态映射 |
| | | const statusMap = { |
| | | '0': '<el-tag type="warning">待执行</el-tag>', |
| | | '1': '<el-tag type="primary">执行中</el-tag>', |
| | | '2': '<el-tag type="success">已完成</el-tag>', |
| | | '3': '<el-tag type="danger">已取消</el-tag>', |
| | | }; |
| | | |
| | | // 类型映射 |
| | | const typeMap = { |
| | | 0: "定期巡检", |
| | | 1: "临时巡检", |
| | | 2: "故障巡检", |
| | | }; |
| | | |
| | | // 表格列配置 |
| | | const columns = [ |
| | | { |
| | | label: "巡检名称", |
| | | align: "center", |
| | | prop: "name", |
| | | }, |
| | | { |
| | | label: "巡检类型", |
| | | align: "center", |
| | | prop: "type", |
| | | formatter: (row) => { |
| | | return typeMap[row.type]; |
| | | }, |
| | | }, |
| | | { |
| | | label: "巡检状态", |
| | | align: "center", |
| | | prop: "status", |
| | | formatter: (row) => { |
| | | return statusMap[row.status]; |
| | | }, |
| | | }, |
| | | { |
| | | label: "计划开始时间", |
| | | align: "center", |
| | | prop: "planStartTime", |
| | | }, |
| | | { |
| | | label: "计划结束时间", |
| | | align: "center", |
| | | prop: "planEndTime", |
| | | }, |
| | | { |
| | | label: "实际开始时间", |
| | | align: "center", |
| | | prop: "actualStartTime", |
| | | formatter: (row) => { |
| | | return row.actualStartTime || "-"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "实际结束时间", |
| | | align: "center", |
| | | prop: "actualEndTime", |
| | | formatter: (row) => { |
| | | return row.actualEndTime || "-"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "巡检人员", |
| | | align: "center", |
| | | prop: "inspector", |
| | | }, |
| | | { |
| | | label: "巡检区域", |
| | | align: "center", |
| | | prop: "area", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 180, |
| | | operation: [ |
| | | { |
| | | name: "编辑", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | edit(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "开始执行", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | startInspection(row); |
| | | }, |
| | | visible: (row) => { |
| | | return row.status === "0"; |
| | | }, |
| | | }, |
| | | { |
| | | name: "完成巡检", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | completeInspection(row); |
| | | }, |
| | | visible: (row) => { |
| | | return row.status === "1"; |
| | | }, |
| | | }, |
| | | { |
| | | name: "取消巡检", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | cancelInspection(row); |
| | | }, |
| | | visible: (row) => { |
| | | return ["0", "1"].includes(row.status); |
| | | }, |
| | | }, |
| | | { |
| | | name: "删除", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | deleteRow(row.id); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | | |
| | | // 过滤后的数据 |
| | | const filteredData = computed(() => { |
| | | return mockData.filter((item) => { |
| | | const nameMatch = !filters.name || item.name.includes(filters.name); |
| | | const statusMatch = !filters.status || item.status === filters.status; |
| | | const typeMatch = !filters.type || item.type === filters.type; |
| | | return nameMatch && statusMatch && typeMatch; |
| | | }); |
| | | }); |
| | | |
| | | // 获取表格数据 |
| | | const getTableData = () => { |
| | | pagination.total = filteredData.value.length; |
| | | const start = (pagination.currentPage - 1) * pagination.pageSize; |
| | | const end = start + pagination.pageSize; |
| | | dataList.value = filteredData.value.slice(start, end); |
| | | }; |
| | | |
| | | // 重置过滤器 |
| | | const resetFilters = () => { |
| | | filters.name = ""; |
| | | filters.status = ""; |
| | | filters.type = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | // 分页变化 |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | // 多选处理 |
| | | const handleSelectionChange = (selectionList) => { |
| | | multipleList.value = selectionList; |
| | | }; |
| | | |
| | | // 新增 |
| | | const add = () => { |
| | | dialogTitle.value = "新增巡检"; |
| | | formData.id = ""; |
| | | formData.name = ""; |
| | | formData.type = "0"; |
| | | formData.status = "0"; |
| | | formData.planStartTime = ""; |
| | | formData.planEndTime = ""; |
| | | formData.actualStartTime = ""; |
| | | formData.actualEndTime = ""; |
| | | formData.inspector = ""; |
| | | formData.area = ""; |
| | | formData.content = ""; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // 编辑 |
| | | const edit = (row) => { |
| | | dialogTitle.value = "编辑巡检"; |
| | | formData.id = row.id; |
| | | formData.name = row.name; |
| | | formData.type = row.type; |
| | | formData.status = row.status; |
| | | formData.planStartTime = row.planStartTime; |
| | | formData.planEndTime = row.planEndTime; |
| | | formData.actualStartTime = row.actualStartTime; |
| | | formData.actualEndTime = row.actualEndTime; |
| | | formData.inspector = row.inspector; |
| | | formData.area = row.area; |
| | | formData.content = row.content; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // 保存数据 |
| | | const saveData = () => { |
| | | if ( |
| | | !formData.name || |
| | | !formData.planStartTime || |
| | | !formData.planEndTime || |
| | | !formData.inspector || |
| | | !formData.area |
| | | ) { |
| | | ElMessage.warning("请填写必填字段"); |
| | | return; |
| | | } |
| | | |
| | | if (formData.id) { |
| | | // 编辑 |
| | | const index = mockData.findIndex((item) => item.id === formData.id); |
| | | if (index !== -1) { |
| | | mockData[index] = { |
| | | ...mockData[index], |
| | | ...formData, |
| | | }; |
| | | ElMessage.success("编辑成功"); |
| | | } |
| | | } else { |
| | | // 新增 |
| | | const newId = Math.max(...mockData.map((item) => item.id), 0) + 1; |
| | | mockData.unshift({ |
| | | ...formData, |
| | | id: newId, |
| | | }); |
| | | ElMessage.success("新增成功"); |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | }; |
| | | |
| | | // 开始巡检 |
| | | const startInspection = (row) => { |
| | | const index = mockData.findIndex((item) => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "1"; |
| | | mockData[index].actualStartTime = dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | ElMessage.success("巡检已开始"); |
| | | getTableData(); |
| | | } |
| | | }; |
| | | |
| | | // 完成巡检 |
| | | const completeInspection = (row) => { |
| | | const index = mockData.findIndex((item) => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "2"; |
| | | mockData[index].actualEndTime = dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | ElMessage.success("巡检已完成"); |
| | | getTableData(); |
| | | } |
| | | }; |
| | | |
| | | // 取消巡检 |
| | | const cancelInspection = (row) => { |
| | | ElMessageBox.confirm("确定要取消该巡检吗?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | const index = mockData.findIndex((item) => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "3"; |
| | | ElMessage.success("巡检已取消"); |
| | | getTableData(); |
| | | } |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // 删除 |
| | | const deleteRow = (id) => { |
| | | ElMessageBox.confirm("此操作将永久删除该巡检记录, 是否继续?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | const index = mockData.findIndex((item) => item.id === id); |
| | | if (index !== -1) { |
| | | mockData.splice(index, 1); |
| | | ElMessage.success("删除成功"); |
| | | getTableData(); |
| | | } |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // 批量删除 |
| | | const batchDelete = () => { |
| | | if (multipleList.value.length === 0) { |
| | | ElMessage.warning("请选择要删除的数据"); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("此操作将永久删除所选巡检记录, 是否继续?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | const ids = multipleList.value.map((item) => item.id); |
| | | mockData.forEach((item, index) => { |
| | | if (ids.includes(item.id)) { |
| | | mockData.splice(index, 1); |
| | | } |
| | | }); |
| | | ElMessage.success("删除成功"); |
| | | getTableData(); |
| | | multipleList.value = []; |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // 初始化数据 |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | <style> |
| | | |
| | | <style lang="scss" scoped> |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div></div> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="设备名称"> |
| | | <el-input |
| | | v-model="filters.name" |
| | | style="width: 240px" |
| | | placeholder="请输入设备名称" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @change="getTableData" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="设备状态"> |
| | | <el-select |
| | | v-model="filters.status" |
| | | style="width: 240px" |
| | | placeholder="请选择设备状态" |
| | | clearable |
| | | @change="getTableData" |
| | | > |
| | | <el-option label="在线" value="1"></el-option> |
| | | <el-option label="离线" value="0"></el-option> |
| | | <el-option label="故障" value="2"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="设备类型"> |
| | | <el-select |
| | | v-model="filters.type" |
| | | style="width: 240px" |
| | | placeholder="请选择设备类型" |
| | | clearable |
| | | @change="getTableData" |
| | | > |
| | | <el-option label="门禁设备" value="0"></el-option> |
| | | <el-option label="环境监测设备" value="1"></el-option> |
| | | <el-option label="视频监控设备" value="2"></el-option> |
| | | <el-option label="智能道闸" value="3"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">搜索</el-button> |
| | | <el-button @click="resetFilters">重置</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="monitor-container"> |
| | | <div class="monitor-stats"> |
| | | <div class="stat-card"> |
| | | <div class="stat-title">在线设备</div> |
| | | <div class="stat-value online">{{ onlineCount }}</div> |
| | | </div> |
| | | <div class="stat-card"> |
| | | <div class="stat-title">离线设备</div> |
| | | <div class="stat-value offline">{{ offlineCount }}</div> |
| | | </div> |
| | | <div class="stat-card"> |
| | | <div class="stat-title">故障设备</div> |
| | | <div class="stat-value error">{{ errorCount }}</div> |
| | | </div> |
| | | <div class="stat-card"> |
| | | <div class="stat-title">设备总数</div> |
| | | <div class="stat-value total">{{ totalCount }}</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | </PIMTable> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 设备详情弹窗 --> |
| | | <el-dialog |
| | | v-model="detailVisible" |
| | | :title="`设备详情 - ${selectedDevice.name}`" |
| | | width="800px" |
| | | > |
| | | <div v-if="selectedDevice" class="device-detail"> |
| | | <div class="detail-section"> |
| | | <h3>基本信息</h3> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="设备名称">{{ |
| | | selectedDevice.name |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="设备类型">{{ |
| | | typeMap[selectedDevice.type] |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="设备状态">{{ |
| | | statusMap[selectedDevice.status] |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="设备编号">{{ |
| | | selectedDevice.code |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="安装位置">{{ |
| | | selectedDevice.location |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="安装时间">{{ |
| | | selectedDevice.installTime |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="最后维护时间">{{ |
| | | selectedDevice.maintainTime |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="责任人">{{ |
| | | selectedDevice.responsiblePerson |
| | | }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <h3>实时数据</h3> |
| | | <div class="realtime-data"> |
| | | <div |
| | | v-for="data in selectedDevice.realtimeData" |
| | | :key="data.name" |
| | | class="data-item" |
| | | > |
| | | <div class="data-name">{{ data.name }}</div> |
| | | <div class="data-value">{{ data.value }}{{ data.unit }}</div> |
| | | <div class="data-status" :class="data.status"> |
| | | {{ data.status === "normal" ? "正常" : "异常" }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <h3>控制操作</h3> |
| | | <div class="control-buttons"> |
| | | <el-button |
| | | type="primary" |
| | | @click="controlDevice('restart')" |
| | | :disabled="selectedDevice.status !== '1'" |
| | | >重启设备</el-button |
| | | > |
| | | <el-button |
| | | type="warning" |
| | | @click="controlDevice('reset')" |
| | | :disabled="selectedDevice.status !== '1'" |
| | | >重置设备</el-button |
| | | > |
| | | <el-button |
| | | type="danger" |
| | | @click="controlDevice('shutdown')" |
| | | :disabled="selectedDevice.status !== '1'" |
| | | >关闭设备</el-button |
| | | > |
| | | <el-button |
| | | type="success" |
| | | @click="controlDevice('update')" |
| | | :disabled="selectedDevice.status !== '1'" |
| | | >固件更新</el-button |
| | | > |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="detailVisible = false">关闭</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | <script> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from "vue"; |
| | | import { |
| | | Search |
| | | } from "@element-plus/icons-vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | |
| | | // 定义假数据 |
| | | const mockDevices = [ |
| | | { |
| | | id: 1, |
| | | name: "东门门禁控制器", |
| | | code: "DEV-20250001", |
| | | type: "0", |
| | | status: "1", |
| | | location: "东门入口", |
| | | installTime: "2025-01-15", |
| | | maintainTime: "2025-12-20", |
| | | responsiblePerson: "张三", |
| | | realtimeData: [ |
| | | { name: "温度", value: 23.5, unit: "℃", status: "normal" }, |
| | | { name: "湿度", value: 45, unit: "%", status: "normal" }, |
| | | { name: "运行时长", value: 12560, unit: "h", status: "normal" }, |
| | | { name: "门禁开关次数", value: 234, unit: "次/天", status: "normal" }, |
| | | ], |
| | | }, |
| | | { |
| | | id: 2, |
| | | name: "西门视频监控", |
| | | code: "DEV-20250002", |
| | | type: "2", |
| | | status: "1", |
| | | location: "西门出口", |
| | | installTime: "2025-02-20", |
| | | maintainTime: "2025-11-15", |
| | | responsiblePerson: "李四", |
| | | realtimeData: [ |
| | | { name: "视频清晰度", value: 1080, unit: "P", status: "normal" }, |
| | | { name: "帧率", value: 25, unit: "fps", status: "normal" }, |
| | | { name: "存储空间", value: 78, unit: "%", status: "warning" }, |
| | | { name: "网络延迟", value: 12, unit: "ms", status: "normal" }, |
| | | ], |
| | | }, |
| | | { |
| | | id: 3, |
| | | name: "南门环境监测仪", |
| | | code: "DEV-20250003", |
| | | type: "1", |
| | | status: "2", |
| | | location: "南门广场", |
| | | installTime: "2025-03-10", |
| | | maintainTime: "2025-10-05", |
| | | responsiblePerson: "王五", |
| | | realtimeData: [ |
| | | { name: "PM2.5", value: 85, unit: "μg/m³", status: "error" }, |
| | | { name: "PM10", value: 120, unit: "μg/m³", status: "error" }, |
| | | { name: "噪声", value: 65, unit: "dB", status: "normal" }, |
| | | { name: "风速", value: 1.2, unit: "m/s", status: "normal" }, |
| | | ], |
| | | }, |
| | | { |
| | | id: 4, |
| | | name: "北门智能道闸", |
| | | code: "DEV-20250004", |
| | | type: "3", |
| | | status: "0", |
| | | location: "北门入口", |
| | | installTime: "2025-04-05", |
| | | maintainTime: "2025-09-20", |
| | | responsiblePerson: "赵六", |
| | | realtimeData: [ |
| | | { name: "开关次数", value: 156, unit: "次/天", status: "normal" }, |
| | | { name: "运行电压", value: 220, unit: "V", status: "normal" }, |
| | | { name: "运行电流", value: 5.2, unit: "A", status: "normal" }, |
| | | { name: "故障代码", value: "E001", unit: "", status: "error" }, |
| | | ], |
| | | }, |
| | | { |
| | | id: 5, |
| | | name: "中控室服务器", |
| | | code: "DEV-20250005", |
| | | type: "1", |
| | | status: "1", |
| | | location: "中控室", |
| | | installTime: "2025-05-15", |
| | | maintainTime: "2025-12-01", |
| | | responsiblePerson: "孙七", |
| | | realtimeData: [ |
| | | { name: "CPU使用率", value: 45, unit: "%", status: "normal" }, |
| | | { name: "内存使用率", value: 68, unit: "%", status: "warning" }, |
| | | { name: "磁盘使用率", value: 82, unit: "%", status: "warning" }, |
| | | { name: "网络流量", value: 125, unit: "Mbps", status: "normal" }, |
| | | ], |
| | | }, |
| | | ]; |
| | | |
| | | // 响应式数据 |
| | | const filters = reactive({ |
| | | name: "", |
| | | status: "", |
| | | type: "", |
| | | }); |
| | | |
| | | const dataList = ref([]); |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const detailVisible = ref(false); |
| | | const selectedDevice = ref({}); |
| | | |
| | | // 状态映射 |
| | | const statusMap = { |
| | | 0: '<el-tag type="warning">离线</el-tag>', |
| | | 1: '<el-tag type="success">在线</el-tag>', |
| | | 2: '<el-tag type="danger">故障</el-tag>', |
| | | }; |
| | | |
| | | // 类型映射 |
| | | const typeMap = { |
| | | 0: "门禁设备", |
| | | 1: "环境监测设备", |
| | | 2: "视频监控设备", |
| | | 3: "智能道闸", |
| | | }; |
| | | |
| | | // 设备数量统计 |
| | | const onlineCount = computed(() => { |
| | | return mockDevices.filter((device) => device.status === "1").length; |
| | | }); |
| | | |
| | | const offlineCount = computed(() => { |
| | | return mockDevices.filter((device) => device.status === "0").length; |
| | | }); |
| | | |
| | | const errorCount = computed(() => { |
| | | return mockDevices.filter((device) => device.status === "2").length; |
| | | }); |
| | | |
| | | const totalCount = computed(() => { |
| | | return mockDevices.length; |
| | | }); |
| | | |
| | | // 表格列配置 |
| | | const columns = [ |
| | | { |
| | | label: "设备名称", |
| | | align: "center", |
| | | prop: "name", |
| | | }, |
| | | { |
| | | label: "设备编号", |
| | | align: "center", |
| | | prop: "code", |
| | | }, |
| | | { |
| | | label: "设备类型", |
| | | align: "center", |
| | | prop: "type", |
| | | formatter: (row) => { |
| | | return typeMap[row.type]; |
| | | }, |
| | | }, |
| | | { |
| | | label: "设备状态", |
| | | align: "center", |
| | | prop: "status", |
| | | formatter: (row) => { |
| | | return statusMap[row.status]; |
| | | }, |
| | | }, |
| | | { |
| | | label: "安装位置", |
| | | align: "center", |
| | | prop: "location", |
| | | }, |
| | | { |
| | | label: "责任人", |
| | | align: "center", |
| | | prop: "responsiblePerson", |
| | | }, |
| | | { |
| | | label: "最后维护时间", |
| | | align: "center", |
| | | prop: "maintainTime", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 140, |
| | | operation: [ |
| | | { |
| | | name: "查看详情", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | viewDetail(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "实时监控", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | realtimeMonitor(row); |
| | | }, |
| | | visible: (row) => { |
| | | return row.status === "1"; |
| | | }, |
| | | }, |
| | | { |
| | | name: "重启设备", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | controlDevice("restart", row); |
| | | }, |
| | | visible: (row) => { |
| | | return row.status === "1"; |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | | |
| | | // 过滤后的数据 |
| | | const filteredData = computed(() => { |
| | | return mockDevices.filter((item) => { |
| | | const nameMatch = !filters.name || item.name.includes(filters.name); |
| | | const statusMatch = !filters.status || item.status === filters.status; |
| | | const typeMatch = !filters.type || item.type === filters.type; |
| | | return nameMatch && statusMatch && typeMatch; |
| | | }); |
| | | }); |
| | | |
| | | // 获取表格数据 |
| | | const getTableData = () => { |
| | | pagination.total = filteredData.value.length; |
| | | const start = (pagination.currentPage - 1) * pagination.pageSize; |
| | | const end = start + pagination.pageSize; |
| | | dataList.value = filteredData.value.slice(start, end); |
| | | }; |
| | | |
| | | // 重置过滤器 |
| | | const resetFilters = () => { |
| | | filters.name = ""; |
| | | filters.status = ""; |
| | | filters.type = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | // 分页变化 |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | // 查看设备详情 |
| | | const viewDetail = (row) => { |
| | | selectedDevice.value = row; |
| | | detailVisible.value = true; |
| | | }; |
| | | |
| | | // 实时监控 |
| | | const realtimeMonitor = (row) => { |
| | | ElMessage.info(`正在跳转到${row.name}的实时监控页面...`); |
| | | // 这里可以跳转到实时监控页面或打开监控窗口 |
| | | }; |
| | | |
| | | // 设备控制 |
| | | const controlDevice = (action, row = selectedDevice.value) => { |
| | | const actionMap = { |
| | | restart: "重启", |
| | | reset: "重置", |
| | | shutdown: "关闭", |
| | | update: "固件更新", |
| | | }; |
| | | |
| | | ElMessageBox.confirm( |
| | | `确定要对设备 ${row.name} 执行${actionMap[action]}操作吗?`, |
| | | "提示", |
| | | { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | } |
| | | ) |
| | | .then(() => { |
| | | ElMessage.success(`设备${actionMap[action]}操作已发送`); |
| | | // 模拟设备响应 |
| | | setTimeout(() => { |
| | | ElMessage.success(`设备${actionMap[action]}操作执行成功`); |
| | | }, 1000); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // 定时更新设备状态(模拟实时变化) |
| | | const updateDeviceStatus = () => { |
| | | // 随机更新一些设备的状态和实时数据 |
| | | mockDevices.forEach((device) => { |
| | | if (Math.random() > 0.8) { |
| | | // 80%概率不更新 |
| | | return; |
| | | } |
| | | |
| | | // 随机更新状态 |
| | | const statuses = ["0", "1", "2"]; |
| | | device.status = statuses[Math.floor(Math.random() * statuses.length)]; |
| | | |
| | | // 更新实时数据 |
| | | device.realtimeData.forEach((data) => { |
| | | if (data.name === "温度") { |
| | | data.value = (20 + Math.random() * 10).toFixed(1); |
| | | data.status = data.value > 30 ? "error" : "normal"; |
| | | } else if (data.name === "湿度") { |
| | | data.value = Math.floor(30 + Math.random() * 50); |
| | | data.status = data.value > 80 ? "error" : "normal"; |
| | | } else if (data.name === "CPU使用率") { |
| | | data.value = Math.floor(20 + Math.random() * 60); |
| | | data.status = |
| | | data.value > 80 ? "error" : data.value > 60 ? "warning" : "normal"; |
| | | } |
| | | }); |
| | | }); |
| | | |
| | | // 刷新表格数据 |
| | | getTableData(); |
| | | }; |
| | | |
| | | // 初始化数据 |
| | | onMounted(() => { |
| | | getTableData(); |
| | | // 每5秒更新一次设备状态 |
| | | const interval = setInterval(updateDeviceStatus, 5000); |
| | | |
| | | // 清理定时器 |
| | | return () => clearInterval(interval); |
| | | }); |
| | | </script> |
| | | <style> |
| | | |
| | | <style lang="scss" scoped> |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |
| | | |
| | | .monitor-container { |
| | | .monitor-stats { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | |
| | | .stat-card { |
| | | flex: 1; |
| | | background: #fff; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | text-align: center; |
| | | |
| | | .stat-title { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .stat-value { |
| | | font-size: 32px; |
| | | font-weight: bold; |
| | | |
| | | &.online { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | &.offline { |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | &.error { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | &.total { |
| | | color: #409eff; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .device-detail { |
| | | .detail-section { |
| | | margin-bottom: 20px; |
| | | |
| | | h3 { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | margin-bottom: 10px; |
| | | color: #303133; |
| | | } |
| | | } |
| | | |
| | | .realtime-data { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, 1fr); |
| | | gap: 15px; |
| | | |
| | | .data-item { |
| | | background: #f5f7fa; |
| | | padding: 15px; |
| | | border-radius: 8px; |
| | | |
| | | .data-name { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .data-value { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .data-status { |
| | | font-size: 12px; |
| | | padding: 2px 8px; |
| | | border-radius: 10px; |
| | | |
| | | &.normal { |
| | | background: #f0f9eb; |
| | | color: #67c23a; |
| | | } |
| | | |
| | | &.warning { |
| | | background: #fdf6ec; |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | &.error { |
| | | background: #fef0f0; |
| | | color: #f56c6c; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .control-buttons { |
| | | display: flex; |
| | | gap: 10px; |
| | | margin-top: 10px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div></div> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="车牌号码"> |
| | | <el-input |
| | | v-model="filters.plateNumber" |
| | | style="width: 240px" |
| | | placeholder="请输入车牌号码" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @change="getTableData" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="车辆类型"> |
| | | <el-select |
| | | v-model="filters.vehicleType" |
| | | style="width: 240px" |
| | | placeholder="请选择车辆类型" |
| | | clearable |
| | | @change="getTableData" |
| | | > |
| | | <el-option label="小型车" value="0"></el-option> |
| | | <el-option label="中型车" value="1"></el-option> |
| | | <el-option label="大型车" value="2"></el-option> |
| | | <el-option label="特种车" value="3"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="进出方向"> |
| | | <el-select |
| | | v-model="filters.direction" |
| | | style="width: 240px" |
| | | placeholder="请选择进出方向" |
| | | clearable |
| | | @change="getTableData" |
| | | > |
| | | <el-option label="进" value="0"></el-option> |
| | | <el-option label="出" value="1"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="进出时间"> |
| | | <el-date-picker |
| | | v-model="filters.timeRange" |
| | | type="daterange" |
| | | start-placeholder="开始时间" |
| | | end-placeholder="结束时间" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | format="YYYY-MM-DD HH:mm" |
| | | clearable |
| | | @change="changeTimeRange" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">搜索</el-button> |
| | | <el-button @click="resetFilters">重置</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="vehicle-stats"> |
| | | <div class="stat-card"> |
| | | <div class="stat-title">今日进车数</div> |
| | | <div class="stat-value in">{{ todayInCount }}</div> |
| | | </div> |
| | | <div class="stat-card"> |
| | | <div class="stat-title">今日出车数</div> |
| | | <div class="stat-value out">{{ todayOutCount }}</div> |
| | | </div> |
| | | <div class="stat-card"> |
| | | <div class="stat-title">当前在场车辆</div> |
| | | <div class="stat-value present">{{ presentCount }}</div> |
| | | </div> |
| | | <div class="stat-card"> |
| | | <div class="stat-title">今日总流量</div> |
| | | <div class="stat-value total">{{ todayTotalCount }}</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <!-- 车辆详情弹窗 --> |
| | | <el-dialog |
| | | v-model="detailVisible" |
| | | :title="`车辆详情 - ${selectedVehicle.plateNumber}`" |
| | | width="700px" |
| | | > |
| | | <div v-if="selectedVehicle" class="vehicle-detail"> |
| | | <div class="detail-section"> |
| | | <h3>基本信息</h3> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="车牌号码">{{ |
| | | selectedVehicle.plateNumber |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="车辆类型">{{ |
| | | typeMap[selectedVehicle.vehicleType] |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="车辆颜色">{{ |
| | | selectedVehicle.color |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="进出方向">{{ |
| | | selectedVehicle.direction === "0" ? "进" : "出" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="进出时间">{{ |
| | | selectedVehicle.timestamp |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="通道名称">{{ |
| | | selectedVehicle.channelName |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="识别方式">{{ |
| | | selectedVehicle.identifyMethod |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="车辆状态">{{ |
| | | statusMap[selectedVehicle.status] |
| | | }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <h3>车辆图片</h3> |
| | | <div class="vehicle-images"> |
| | | <div class="image-item"> |
| | | <div class="image-title">正面图片</div> |
| | | <el-image |
| | | :src="selectedVehicle.frontImage" |
| | | fit="cover" |
| | | style="width: 100%; height: 200px" |
| | | :preview-src-list="[ |
| | | selectedVehicle.frontImage, |
| | | selectedVehicle.sideImage, |
| | | ]" |
| | | ></el-image> |
| | | </div> |
| | | <div class="image-item"> |
| | | <div class="image-title">侧面图片</div> |
| | | <el-image |
| | | :src="selectedVehicle.sideImage" |
| | | fit="cover" |
| | | style="width: 100%; height: 200px" |
| | | :preview-src-list="[ |
| | | selectedVehicle.sideImage, |
| | | selectedVehicle.frontImage, |
| | | ]" |
| | | ></el-image> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="detailVisible = false">关闭</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | <script> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // 定义假数据 |
| | | const mockVehicles = [ |
| | | { |
| | | id: 1, |
| | | plateNumber: "京A12345", |
| | | vehicleType: "0", |
| | | color: "白色", |
| | | direction: "0", |
| | | timestamp: "2025-12-31 08:15:30", |
| | | channelName: "东门通道", |
| | | identifyMethod: "车牌识别", |
| | | status: "0", |
| | | frontImage: "https://picsum.photos/id/1024/800/600", |
| | | sideImage: "https://picsum.photos/id/1025/800/600", |
| | | duration: "00:00:00", |
| | | present: true, |
| | | }, |
| | | { |
| | | id: 2, |
| | | plateNumber: "沪B67890", |
| | | vehicleType: "1", |
| | | color: "黑色", |
| | | direction: "1", |
| | | timestamp: "2025-12-31 08:10:20", |
| | | channelName: "西门通道", |
| | | identifyMethod: "车牌识别", |
| | | status: "1", |
| | | frontImage: "https://picsum.photos/id/1026/800/600", |
| | | sideImage: "https://picsum.photos/id/1027/800/600", |
| | | duration: "01:25:30", |
| | | present: false, |
| | | }, |
| | | { |
| | | id: 3, |
| | | plateNumber: "粤C54321", |
| | | vehicleType: "0", |
| | | color: "红色", |
| | | direction: "0", |
| | | timestamp: "2025-12-31 08:05:15", |
| | | channelName: "南门通道", |
| | | identifyMethod: "IC卡识别", |
| | | status: "0", |
| | | frontImage: "https://picsum.photos/id/1028/800/600", |
| | | sideImage: "https://picsum.photos/id/1029/800/600", |
| | | duration: "00:05:45", |
| | | present: true, |
| | | }, |
| | | { |
| | | id: 4, |
| | | plateNumber: "川D98765", |
| | | vehicleType: "2", |
| | | color: "蓝色", |
| | | direction: "0", |
| | | timestamp: "2025-12-31 08:00:00", |
| | | channelName: "北门通道", |
| | | identifyMethod: "车牌识别", |
| | | status: "0", |
| | | frontImage: "https://picsum.photos/id/1030/800/600", |
| | | sideImage: "https://picsum.photos/id/1031/800/600", |
| | | duration: "00:10:30", |
| | | present: true, |
| | | }, |
| | | { |
| | | id: 5, |
| | | plateNumber: "苏E13579", |
| | | vehicleType: "0", |
| | | color: "银色", |
| | | direction: "1", |
| | | timestamp: "2025-12-31 07:55:45", |
| | | channelName: "东门通道", |
| | | identifyMethod: "车牌识别", |
| | | status: "1", |
| | | frontImage: "https://picsum.photos/id/1032/800/600", |
| | | sideImage: "https://picsum.photos/id/1033/800/600", |
| | | duration: "02:30:15", |
| | | present: false, |
| | | }, |
| | | { |
| | | id: 6, |
| | | plateNumber: "浙F24680", |
| | | vehicleType: "3", |
| | | color: "黄色", |
| | | direction: "0", |
| | | timestamp: "2025-12-31 07:50:30", |
| | | channelName: "南门通道", |
| | | identifyMethod: "人工登记", |
| | | status: "0", |
| | | frontImage: "https://picsum.photos/id/1034/800/600", |
| | | sideImage: "https://picsum.photos/id/1035/800/600", |
| | | duration: "00:15:30", |
| | | present: true, |
| | | }, |
| | | { |
| | | id: 7, |
| | | plateNumber: "鲁G97531", |
| | | vehicleType: "1", |
| | | color: "绿色", |
| | | direction: "1", |
| | | timestamp: "2025-12-31 07:45:15", |
| | | channelName: "西门通道", |
| | | identifyMethod: "车牌识别", |
| | | status: "1", |
| | | frontImage: "https://picsum.photos/id/1036/800/600", |
| | | sideImage: "https://picsum.photos/id/1037/800/600", |
| | | duration: "01:45:30", |
| | | present: false, |
| | | }, |
| | | { |
| | | id: 8, |
| | | plateNumber: "豫H86420", |
| | | vehicleType: "0", |
| | | color: "灰色", |
| | | direction: "0", |
| | | timestamp: "2025-12-31 07:40:00", |
| | | channelName: "北门通道", |
| | | identifyMethod: "IC卡识别", |
| | | status: "0", |
| | | frontImage: "https://picsum.photos/id/1038/800/600", |
| | | sideImage: "https://picsum.photos/id/1039/800/600", |
| | | duration: "00:20:15", |
| | | present: true, |
| | | }, |
| | | ]; |
| | | |
| | | // 响应式数据 |
| | | const filters = reactive({ |
| | | plateNumber: "", |
| | | vehicleType: "", |
| | | direction: "", |
| | | timeRange: [], |
| | | }); |
| | | |
| | | const dataList = ref([]); |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const detailVisible = ref(false); |
| | | const selectedVehicle = ref({}); |
| | | |
| | | // 时间范围 |
| | | const timeRange = reactive({ |
| | | startTime: "", |
| | | endTime: "", |
| | | }); |
| | | |
| | | // 状态映射 |
| | | const statusMap = { |
| | | '0': '<el-tag type="success">正常</el-tag>', |
| | | '1': '<el-tag type="info">已离开</el-tag>', |
| | | '2': '<el-tag type="warning">异常</el-tag>', |
| | | }; |
| | | |
| | | // 类型映射 |
| | | const typeMap = { |
| | | 0: "小型车", |
| | | 1: "中型车", |
| | | 2: "大型车", |
| | | 3: "特种车", |
| | | }; |
| | | |
| | | // 统计数据 |
| | | const todayInCount = computed(() => { |
| | | const today = dayjs().format("YYYY-MM-DD"); |
| | | return mockVehicles.filter((vehicle) => { |
| | | return vehicle.direction === "0" && vehicle.timestamp.startsWith(today); |
| | | }).length; |
| | | }); |
| | | |
| | | const todayOutCount = computed(() => { |
| | | const today = dayjs().format("YYYY-MM-DD"); |
| | | return mockVehicles.filter((vehicle) => { |
| | | return vehicle.direction === "1" && vehicle.timestamp.startsWith(today); |
| | | }).length; |
| | | }); |
| | | |
| | | const presentCount = computed(() => { |
| | | return mockVehicles.filter((vehicle) => vehicle.present).length; |
| | | }); |
| | | |
| | | const todayTotalCount = computed(() => { |
| | | const today = dayjs().format("YYYY-MM-DD"); |
| | | return mockVehicles.filter((vehicle) => { |
| | | return vehicle.timestamp.startsWith(today); |
| | | }).length; |
| | | }); |
| | | |
| | | // 表格列配置 |
| | | const columns = [ |
| | | { |
| | | label: "车牌号码", |
| | | align: "center", |
| | | prop: "plateNumber", |
| | | }, |
| | | { |
| | | label: "车辆类型", |
| | | align: "center", |
| | | prop: "vehicleType", |
| | | formatter: (row) => { |
| | | return typeMap[row.vehicleType]; |
| | | }, |
| | | }, |
| | | { |
| | | label: "车辆颜色", |
| | | align: "center", |
| | | prop: "color", |
| | | }, |
| | | { |
| | | label: "进出方向", |
| | | align: "center", |
| | | prop: "direction", |
| | | formatter: (row) => { |
| | | return row.direction === "0" ? '<el-tag type="success">进</el-tag>' : '<el-tag type="info">出</el-tag>'; |
| | | }, |
| | | }, |
| | | { |
| | | label: "进出时间", |
| | | align: "center", |
| | | prop: "timestamp", |
| | | }, |
| | | { |
| | | label: "通道名称", |
| | | align: "center", |
| | | prop: "channelName", |
| | | }, |
| | | { |
| | | label: "识别方式", |
| | | align: "center", |
| | | prop: "identifyMethod", |
| | | }, |
| | | { |
| | | label: "车辆状态", |
| | | align: "center", |
| | | prop: "status", |
| | | formatter: (row) => { |
| | | return statusMap[row.status]; |
| | | }, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 140, |
| | | operation: [ |
| | | { |
| | | name: "查看详情", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | viewDetail(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | | |
| | | // 过滤后的数据 |
| | | const filteredData = computed(() => { |
| | | return mockVehicles.filter((item) => { |
| | | const plateMatch = |
| | | !filters.plateNumber || item.plateNumber.includes(filters.plateNumber); |
| | | const typeMatch = |
| | | !filters.vehicleType || item.vehicleType === filters.vehicleType; |
| | | const directionMatch = |
| | | !filters.direction || item.direction === filters.direction; |
| | | const timeMatch = |
| | | !timeRange.startTime || |
| | | (item.timestamp >= timeRange.startTime && |
| | | item.timestamp <= timeRange.endTime); |
| | | return plateMatch && typeMatch && directionMatch && timeMatch; |
| | | }); |
| | | }); |
| | | |
| | | // 获取表格数据 |
| | | const getTableData = () => { |
| | | pagination.total = filteredData.value.length; |
| | | const start = (pagination.currentPage - 1) * pagination.pageSize; |
| | | const end = start + pagination.pageSize; |
| | | dataList.value = filteredData.value.slice(start, end); |
| | | }; |
| | | |
| | | // 重置过滤器 |
| | | const resetFilters = () => { |
| | | filters.plateNumber = ""; |
| | | filters.vehicleType = ""; |
| | | filters.direction = ""; |
| | | filters.timeRange = []; |
| | | timeRange.startTime = ""; |
| | | timeRange.endTime = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | // 分页变化 |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | // 时间范围变化 |
| | | const changeTimeRange = (value) => { |
| | | if (value && value.length === 2) { |
| | | timeRange.startTime = value[0]; |
| | | timeRange.endTime = value[1]; |
| | | } else { |
| | | timeRange.startTime = ""; |
| | | timeRange.endTime = ""; |
| | | } |
| | | getTableData(); |
| | | }; |
| | | |
| | | // 查看车辆详情 |
| | | const viewDetail = (row) => { |
| | | selectedVehicle.value = row; |
| | | detailVisible.value = true; |
| | | }; |
| | | |
| | | // 初始化数据 |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | <style> |
| | | |
| | | <style lang="scss" scoped> |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |
| | | |
| | | .vehicle-stats { |
| | | display: flex; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | |
| | | .stat-card { |
| | | flex: 1; |
| | | background: #fff; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | text-align: center; |
| | | |
| | | .stat-title { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .stat-value { |
| | | font-size: 32px; |
| | | font-weight: bold; |
| | | |
| | | &.in { |
| | | color: #67c23a; |
| | | } |
| | | |
| | | &.out { |
| | | color: #409eff; |
| | | } |
| | | |
| | | &.present { |
| | | color: #e6a23c; |
| | | } |
| | | |
| | | &.total { |
| | | color: #909399; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .vehicle-detail { |
| | | .detail-section { |
| | | margin-bottom: 20px; |
| | | |
| | | h3 { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | margin-bottom: 10px; |
| | | color: #303133; |
| | | } |
| | | } |
| | | |
| | | .vehicle-images { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, 1fr); |
| | | gap: 15px; |
| | | |
| | | .image-item { |
| | | background: #f5f7fa; |
| | | padding: 15px; |
| | | border-radius: 8px; |
| | | |
| | | .image-title { |
| | | font-size: 14px; |
| | | color: #606266; |
| | | margin-bottom: 10px; |
| | | text-align: center; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | const { VITE_APP_ENV } = env;
|
| | | const baseUrl =
|
| | | VITE_APP_ENV == "development"
|
| | | ? "http://192.168.1.147:7003" // 开发环境后端接口
|
| | | ? "http://10.136.12.71:7003" // 开发环境后端接口
|
| | | : "http://10.136.12.71:7003"; // 生产环境后端接口
|
| | |
|
| | | return {
|