| | |
| | | </el-form-item> |
| | | <el-form-item label="状态"> |
| | | <el-select v-model="filters.status" placeholder="请选择" clearable style="width: 150px"> |
| | | <el-option label="正常" value="normal" /> |
| | | <el-option label="异常" value="abnormal" /> |
| | | <el-option label="漏检" value="missed" /> |
| | | <el-option label="未执行" value="unexecuted" /> |
| | | <el-option label="正常" :value="0" /> |
| | | <el-option label="异常" :value="1" /> |
| | | <el-option label="漏检" :value="2" /> |
| | | <el-option label="未执行" :value="3" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getData">搜索</el-button> |
| | | <el-button @click="resetFilters">重置</el-button> |
| | | <el-button type="success" @click="handleSyncData" icon="Refresh">同步数据</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <PIMTable :column="columns" :tableData="dataList" :page="pagination" @pagination="changePage" /> |
| | | <PIMTable :column="columns" :tableData="dataList" :page="pagination" @pagination="changePage" :tableLoading="tableLoading" /> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ref, reactive, onMounted, nextTick } from "vue"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import * as echarts from "echarts"; |
| | | import { |
| | | getInspectionRecordList, |
| | | getTodayStatistics, |
| | | getTrendStatistics, |
| | | getTypeStatistics, |
| | | syncInspectionData |
| | | } from "@/api/safetyManagement/inspectionReport.js"; |
| | | import { ElMessage } from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | defineOptions({ |
| | | name: "巡检数据报表", |
| | | }); |
| | | |
| | | const todayStats = reactive({ |
| | | completionRate: 95, |
| | | inspectorCount: 8, |
| | | abnormalCount: 2, |
| | | missedCount: 1, |
| | | completionRate: 0, |
| | | inspectorCount: 0, |
| | | abnormalCount: 0, |
| | | missedCount: 0, |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | dateRange: [], |
| | | inspector: "", |
| | | status: "", |
| | | status: null, |
| | | }); |
| | | |
| | | const dataList = ref([]); |
| | | const pagination = reactive({ current: 1, size: 10, total: 0 }); |
| | | const tableLoading = ref(false); |
| | | |
| | | const columns = [ |
| | | { label: "巡检时间", prop: "inspectionTime", align: "center" }, |
| | | { label: "巡检人员", prop: "inspector", align: "center" }, |
| | | { label: "巡检区域", prop: "area", align: "center" }, |
| | | { label: "巡检类型", prop: "type", align: "center" }, |
| | | { label: "状态", prop: "status", align: "center" }, |
| | | { |
| | | label: "巡检类型", |
| | | prop: "type", |
| | | align: "center", |
| | | formatData: (val) => { |
| | | const typeMap = { |
| | | 'daily': '日常巡检', |
| | | 'equipment': '设备巡检', |
| | | 'fire': '消防巡检', |
| | | 'night': '夜间巡检', |
| | | 'safety': '安全巡检', |
| | | 'environment': '环境巡检' |
| | | }; |
| | | return typeMap[val] || val; |
| | | } |
| | | }, |
| | | { |
| | | label: "状态", |
| | | prop: "status", |
| | | align: "center", |
| | | dataType: "tag", |
| | | formatType: (val) => { |
| | | if (val === 0) return 'success'; |
| | | if (val === 1) return 'danger'; |
| | | if (val === 2) return 'warning'; |
| | | return 'info'; |
| | | }, |
| | | formatData: (val) => { |
| | | const statusMap = { 0: '正常', 1: '异常', 2: '漏检', 3: '未执行' }; |
| | | return statusMap[val] || val; |
| | | } |
| | | }, |
| | | { label: "异常情况", prop: "abnormalDesc", align: "center" }, |
| | | ]; |
| | | |
| | | const trendChartRef = ref(null); |
| | | const typeChartRef = ref(null); |
| | | let trendChart = null; |
| | | let typeChart = null; |
| | | |
| | | onMounted(() => { |
| | | initTrendChart(); |
| | | initTypeChart(); |
| | | getTodayStats(); |
| | | getData(); |
| | | nextTick(() => { |
| | | initTrendChart(); |
| | | initTypeChart(); |
| | | }); |
| | | }); |
| | | |
| | | const initTrendChart = () => { |
| | | const chart = echarts.init(trendChartRef.value); |
| | | const option = { |
| | | xAxis: { |
| | | type: "category", |
| | | data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"], |
| | | }, |
| | | yAxis: { type: "value" }, |
| | | series: [ |
| | | { name: "已完成", type: "line", data: [120, 132, 101, 134, 90, 230, 210], smooth: true }, |
| | | { name: "未完成", type: "line", data: [20, 12, 21, 14, 30, 20, 10], smooth: true }, |
| | | ], |
| | | legend: { data: ["已完成", "未完成"], bottom: 0 }, |
| | | grid: { left: "3%", right: "4%", bottom: "10%", containLabel: true }, |
| | | }; |
| | | chart.setOption(option); |
| | | // 获取今日统计 |
| | | const getTodayStats = async () => { |
| | | try { |
| | | const res = await getTodayStatistics(); |
| | | if (res.code === 200) { |
| | | Object.assign(todayStats, res.data); |
| | | } |
| | | } catch (error) { |
| | | console.error('获取今日统计失败', error); |
| | | } |
| | | }; |
| | | |
| | | const initTypeChart = () => { |
| | | const chart = echarts.init(typeChartRef.value); |
| | | const option = { |
| | | series: [ |
| | | { |
| | | type: "pie", |
| | | radius: ["40%", "70%"], |
| | | data: [ |
| | | { value: 335, name: "设备巡检" }, |
| | | { value: 310, name: "安全巡检" }, |
| | | { value: 234, name: "环境巡检" }, |
| | | { value: 135, name: "消防巡检" }, |
| | | ], |
| | | // 初始化趋势图表 |
| | | const initTrendChart = async () => { |
| | | if (!trendChartRef.value) return; |
| | | |
| | | trendChart = echarts.init(trendChartRef.value); |
| | | |
| | | try { |
| | | const endDate = dayjs().format('YYYY-MM-DD'); |
| | | const startDate = dayjs().subtract(6, 'day').format('YYYY-MM-DD'); |
| | | const res = await getTrendStatistics({ startDate, endDate }); |
| | | |
| | | let dates = []; |
| | | let completedData = []; |
| | | let abnormalData = []; |
| | | let missedData = []; |
| | | let unexecutedData = []; |
| | | |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | // 按日期排序 |
| | | const sortedData = res.data.sort((a, b) => a.statDate.localeCompare(b.statDate)); |
| | | dates = sortedData.map(item => dayjs(item.statDate).format('MM-DD')); |
| | | completedData = sortedData.map(item => item.completedCount || 0); |
| | | abnormalData = sortedData.map(item => item.abnormalCount || 0); |
| | | missedData = sortedData.map(item => item.missedCount || 0); |
| | | unexecutedData = sortedData.map(item => item.unexecutedCount || 0); |
| | | } else { |
| | | // 使用默认数据 |
| | | for (let i = 6; i >= 0; i--) { |
| | | dates.push(dayjs().subtract(i, 'day').format('MM-DD')); |
| | | } |
| | | completedData = [2, 1, 2, 2, 1, 4, 3]; |
| | | abnormalData = [1, 0, 0, 1, 0, 1, 0]; |
| | | missedData = [0, 0, 1, 0, 0, 1, 0]; |
| | | unexecutedData = [0, 1, 0, 0, 1, 0, 1]; |
| | | } |
| | | |
| | | const option = { |
| | | tooltip: { trigger: 'axis' }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: dates, |
| | | }, |
| | | ], |
| | | legend: { orient: "vertical", left: "left" }, |
| | | }; |
| | | chart.setOption(option); |
| | | yAxis: { type: "value" }, |
| | | series: [ |
| | | { name: "已完成", type: "line", data: completedData, smooth: true }, |
| | | { name: "异常", type: "line", data: abnormalData, smooth: true }, |
| | | { name: "漏检", type: "line", data: missedData, smooth: true }, |
| | | { name: "未执行", type: "line", data: unexecutedData, smooth: true }, |
| | | ], |
| | | legend: { data: ["已完成", "异常", "漏检", "未执行"], bottom: 0 }, |
| | | grid: { left: "3%", right: "4%", bottom: "15%", containLabel: true }, |
| | | }; |
| | | trendChart.setOption(option); |
| | | } catch (error) { |
| | | console.error('获取趋势数据失败', error); |
| | | } |
| | | }; |
| | | |
| | | const getData = () => {}; |
| | | // 类型映射 |
| | | const typeMap = { |
| | | 'daily': '日常巡检', |
| | | 'equipment': '设备巡检', |
| | | 'fire': '消防巡检', |
| | | 'night': '夜间巡检', |
| | | 'safety': '安全巡检', |
| | | 'environment': '环境巡检' |
| | | }; |
| | | |
| | | // 初始化类型图表 |
| | | const initTypeChart = async () => { |
| | | if (!typeChartRef.value) return; |
| | | |
| | | typeChart = echarts.init(typeChartRef.value); |
| | | |
| | | try { |
| | | const endDate = dayjs().format('YYYY-MM-DD'); |
| | | const startDate = dayjs().subtract(30, 'day').format('YYYY-MM-DD'); |
| | | const res = await getTypeStatistics({ startDate, endDate }); |
| | | |
| | | let pieData = []; |
| | | |
| | | if (res.code === 200 && Array.isArray(res.data)) { |
| | | pieData = res.data.map(item => ({ |
| | | value: item.count || 0, |
| | | name: typeMap[item.type] || item.type |
| | | })); |
| | | } else { |
| | | // 使用默认数据 |
| | | pieData = [ |
| | | { value: 8, name: "日常巡检" }, |
| | | { value: 4, name: "设备巡检" }, |
| | | { value: 3, name: "消防巡检" }, |
| | | { value: 3, name: "夜间巡检" }, |
| | | ]; |
| | | } |
| | | |
| | | const option = { |
| | | tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' }, |
| | | series: [ |
| | | { |
| | | type: "pie", |
| | | radius: ["40%", "70%"], |
| | | data: pieData, |
| | | label: { |
| | | formatter: '{b}\n{c}次 ({d}%)' |
| | | } |
| | | }, |
| | | ], |
| | | legend: { orient: "vertical", left: "left" }, |
| | | }; |
| | | typeChart.setOption(option); |
| | | } catch (error) { |
| | | console.error('获取类型统计失败', error); |
| | | } |
| | | }; |
| | | |
| | | // 获取巡检记录列表 |
| | | const getData = async () => { |
| | | tableLoading.value = true; |
| | | try { |
| | | const params = { |
| | | pageNum: pagination.current, |
| | | pageSize: pagination.size, |
| | | inspector: filters.inspector, |
| | | status: filters.status, |
| | | }; |
| | | |
| | | if (filters.dateRange && filters.dateRange.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | |
| | | const res = await getInspectionRecordList(params); |
| | | if (res.code === 200) { |
| | | dataList.value = res.data.rows || res.data.records || []; |
| | | pagination.total = res.data.total || 0; |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error('获取巡检记录失败'); |
| | | } finally { |
| | | tableLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.dateRange = []; |
| | | filters.inspector = ""; |
| | | filters.status = ""; |
| | | filters.status = null; |
| | | pagination.current = 1; |
| | | getData(); |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.current = page; |
| | | pagination.size = limit; |
| | | getData(); |
| | | }; |
| | | |
| | | // 同步巡检数据 |
| | | const handleSyncData = async () => { |
| | | try { |
| | | const res = await syncInspectionData(); |
| | | if (res.code === 200) { |
| | | ElMessage.success(`同步成功,新增 ${res.data?.count || 0} 条记录`); |
| | | // 刷新列表和统计 |
| | | getData(); |
| | | getTodayStats(); |
| | | initTrendChart(); |
| | | initTypeChart(); |
| | | } else { |
| | | ElMessage.warning(res.msg || '同步失败'); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error(error.response?.data?.msg || '同步失败'); |
| | | } |
| | | }; |
| | | |
| | | // 窗口大小改变时重新渲染图表 |
| | | window.addEventListener('resize', () => { |
| | | trendChart && trendChart.resize(); |
| | | typeChart && typeChart.resize(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |