|
|
@@ -0,0 +1,981 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { nextTick, onMounted, reactive, ref } from 'vue'
|
|
|
+import { clientGet, clientPost } from '@/utils/request.ts'
|
|
|
+import type { BaseResponse, PaginationResponse } from '@/utils/type.ts'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import {
|
|
|
+ Activity,
|
|
|
+ BarChart3,
|
|
|
+ Calendar,
|
|
|
+ Clock,
|
|
|
+ Delete,
|
|
|
+ Edit,
|
|
|
+ Plus,
|
|
|
+ Power,
|
|
|
+ RefreshCw,
|
|
|
+ Search,
|
|
|
+ TrendingUp,
|
|
|
+ Zap
|
|
|
+} from 'lucide-vue-next'
|
|
|
+import * as echarts from 'echarts'
|
|
|
+
|
|
|
+interface ElectricItem {
|
|
|
+ id: number
|
|
|
+ companyName: string
|
|
|
+ electricNumber: string
|
|
|
+ createTime: string
|
|
|
+}
|
|
|
+
|
|
|
+interface ElectricListResponse extends BaseResponse {
|
|
|
+ data: PaginationResponse<ElectricItem>
|
|
|
+}
|
|
|
+
|
|
|
+interface ElectricResponse extends BaseResponse {
|
|
|
+ data: ElectricItem
|
|
|
+}
|
|
|
+
|
|
|
+interface YearData extends BaseResponse {
|
|
|
+ data: {
|
|
|
+ allYearData: number,
|
|
|
+ meterMonthDataList: number[]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+interface MonthData extends BaseResponse {
|
|
|
+ data: {
|
|
|
+ allMonthData: number,
|
|
|
+ meterDayDataList: number[]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+interface DayData extends BaseResponse {
|
|
|
+ data: {
|
|
|
+ dayData: number
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const tableData = ref<ElectricItem[]>([])
|
|
|
+const loading = ref(false)
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const dialogTitle = ref('新增电表')
|
|
|
+const isEdit = ref(false)
|
|
|
+const selectedIds = ref<number[]>([])
|
|
|
+const formRef = ref()
|
|
|
+
|
|
|
+// 图表对话框相关
|
|
|
+const chartDialogVisible = ref(false)
|
|
|
+const currentElectricNumber = ref('')
|
|
|
+const currentCompanyName = ref('')
|
|
|
+const chartLoading = ref(false)
|
|
|
+
|
|
|
+// 图表实例引用
|
|
|
+const yearChartRef = ref()
|
|
|
+const monthChartRef = ref()
|
|
|
+
|
|
|
+// 图表数据
|
|
|
+const chartData = reactive({
|
|
|
+ year: new Date().getFullYear(),
|
|
|
+ month: new Date().getMonth() + 1,
|
|
|
+ yearData: {
|
|
|
+ total: 0,
|
|
|
+ monthlyData: [] as number[]
|
|
|
+ },
|
|
|
+ monthData: {
|
|
|
+ total: 0,
|
|
|
+ dailyData: [] as number[]
|
|
|
+ },
|
|
|
+ dayData: {
|
|
|
+ total: 0
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 分页数据
|
|
|
+const pagination = reactive({
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ total: 0
|
|
|
+})
|
|
|
+
|
|
|
+// 搜索表单
|
|
|
+const searchForm = reactive({
|
|
|
+ companyName: '',
|
|
|
+ electricNumber: ''
|
|
|
+})
|
|
|
+
|
|
|
+// 表单数据
|
|
|
+const formData = reactive({
|
|
|
+ id: 0,
|
|
|
+ companyName: '',
|
|
|
+ electricNumber: ''
|
|
|
+})
|
|
|
+
|
|
|
+// 表单验证规则
|
|
|
+const formRules = {
|
|
|
+ companyName: [
|
|
|
+ { required: true, message: '请输入公司名称', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ electricNumber: [
|
|
|
+ { required: true, message: '请输入电表编号', trigger: 'blur' }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+const getList = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const arr: {
|
|
|
+ value?: string,
|
|
|
+ column: string,
|
|
|
+ type: string
|
|
|
+ }[] = []
|
|
|
+
|
|
|
+ arr.push({
|
|
|
+ column: 'create_time',
|
|
|
+ type: 'orderByDesc'
|
|
|
+ })
|
|
|
+
|
|
|
+ if (searchForm.companyName) {
|
|
|
+ arr.push({
|
|
|
+ value: searchForm.companyName,
|
|
|
+ column: 'company_name',
|
|
|
+ type: 'like'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ if (searchForm.electricNumber) {
|
|
|
+ arr.push({
|
|
|
+ value: searchForm.electricNumber,
|
|
|
+ column: 'electric_number',
|
|
|
+ type: 'like'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ const params = {
|
|
|
+ pageNum: pagination.pageNum,
|
|
|
+ pageSize: pagination.pageSize,
|
|
|
+ conditionJson: encodeURIComponent(JSON.stringify(arr))
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await clientGet<typeof params, ElectricListResponse>('/infrared/companyElectric/findByPage', {
|
|
|
+ params
|
|
|
+ })
|
|
|
+
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ tableData.value = res.data.records
|
|
|
+ pagination.total = res.data.total
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error)
|
|
|
+ ElMessage.error('获取数据失败')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// API 函数
|
|
|
+const getElectricYears = async (electricNumber: string, year: number) => {
|
|
|
+ const res = await clientGet<{
|
|
|
+ meterNumber: string,
|
|
|
+ year: number
|
|
|
+ }, YearData>('/infrared/infraredReadingMeter/selectByYearAndMonthData', {
|
|
|
+ params: {
|
|
|
+ meterNumber: electricNumber,
|
|
|
+ year
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg)
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+const getElectricMonth = async (electricNumber: string, year: number, month: number) => {
|
|
|
+ const res = await clientGet<{
|
|
|
+ meterNumber: string,
|
|
|
+ year: number,
|
|
|
+ month: number
|
|
|
+ }, MonthData>('/infrared/infraredReadingMeter/selectByYearAndMonthWithDayData', {
|
|
|
+ params: {
|
|
|
+ meterNumber: electricNumber,
|
|
|
+ year,
|
|
|
+ month
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg)
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+const getElectricDay = async (electricNumber: string) => {
|
|
|
+ const res = await clientGet<{
|
|
|
+ meterNumber: string
|
|
|
+ }, DayData>('/infrared/infraredReadingMeter/selectTodayNewestData', {
|
|
|
+ params: {
|
|
|
+ meterNumber: electricNumber
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg)
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+// 点击电表编号显示图表
|
|
|
+const handleShowChart = async (row: ElectricItem) => {
|
|
|
+ currentElectricNumber.value = row.electricNumber
|
|
|
+ currentCompanyName.value = row.companyName
|
|
|
+ chartDialogVisible.value = true
|
|
|
+
|
|
|
+ await loadChartData()
|
|
|
+}
|
|
|
+
|
|
|
+// 加载图表数据
|
|
|
+const loadChartData = async () => {
|
|
|
+ chartLoading.value = true
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 并行加载三种数据
|
|
|
+ const [yearResult, monthResult, dayResult] = await Promise.all([
|
|
|
+ getElectricYears(currentElectricNumber.value, chartData.year),
|
|
|
+ getElectricMonth(currentElectricNumber.value, chartData.year, chartData.month),
|
|
|
+ getElectricDay(currentElectricNumber.value)
|
|
|
+ ])
|
|
|
+
|
|
|
+ if (yearResult) {
|
|
|
+ chartData.yearData.total = yearResult.allYearData
|
|
|
+ chartData.yearData.monthlyData = yearResult.meterMonthDataList
|
|
|
+ }
|
|
|
+
|
|
|
+ if (monthResult) {
|
|
|
+ chartData.monthData.total = monthResult.allMonthData
|
|
|
+ chartData.monthData.dailyData = monthResult.meterDayDataList
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dayResult) {
|
|
|
+ chartData.dayData.total = dayResult.dayData
|
|
|
+ }
|
|
|
+
|
|
|
+ // 等待DOM更新后初始化图表
|
|
|
+ await nextTick()
|
|
|
+ initCharts()
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载图表数据失败:', error)
|
|
|
+ ElMessage.error('加载图表数据失败')
|
|
|
+ } finally {
|
|
|
+ chartLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化图表
|
|
|
+const initCharts = () => {
|
|
|
+ initYearChart()
|
|
|
+ initMonthChart()
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化年度图表
|
|
|
+const initYearChart = () => {
|
|
|
+ if (!yearChartRef.value) return
|
|
|
+
|
|
|
+ const chart = echarts.init(yearChartRef.value)
|
|
|
+ const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ title: {
|
|
|
+ text: `${chartData.year}年度用电量统计`,
|
|
|
+ subtext: `总用电量: ${chartData.yearData.total} kWh`,
|
|
|
+ left: 'center'
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ formatter: '{b}: {c} kWh'
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: months
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ name: '用电量 (kWh)'
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ name: '月度用电量',
|
|
|
+ type: 'bar',
|
|
|
+ data: chartData.yearData.monthlyData,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#409EFF'
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ }
|
|
|
+
|
|
|
+ chart.setOption(option)
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化月度图表
|
|
|
+const initMonthChart = () => {
|
|
|
+ if (!monthChartRef.value) return
|
|
|
+
|
|
|
+ const chart = echarts.init(monthChartRef.value)
|
|
|
+ const days = Array.from({ length: chartData.monthData.dailyData.length }, (_, i) => `${i + 1}日`)
|
|
|
+
|
|
|
+ const option = {
|
|
|
+ title: {
|
|
|
+ text: `${chartData.year}年${chartData.month}月用电量统计`,
|
|
|
+ subtext: `总用电量: ${chartData.monthData.total} kWh`,
|
|
|
+ left: 'center'
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ formatter: '{b}: {c} kWh'
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: days
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ name: '用电量 (kWh)'
|
|
|
+ },
|
|
|
+ series: [{
|
|
|
+ name: '日用电量',
|
|
|
+ type: 'line',
|
|
|
+ data: chartData.monthData.dailyData,
|
|
|
+ smooth: true,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#67C23A'
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ }
|
|
|
+
|
|
|
+ chart.setOption(option)
|
|
|
+}
|
|
|
+
|
|
|
+// 计算用电量等级
|
|
|
+const getPowerLevel = (value: number) => {
|
|
|
+ if (value < 50) return { level: 'low', color: '#67C23A', text: '节能' }
|
|
|
+ if (value < 100) return { level: 'medium', color: '#E6A23C', text: '正常' }
|
|
|
+ return { level: 'high', color: '#F56C6C', text: '偏高' }
|
|
|
+}
|
|
|
+
|
|
|
+// 年份/月份变化时重新加载数据
|
|
|
+const handleYearChange = async () => {
|
|
|
+ await loadChartData()
|
|
|
+}
|
|
|
+
|
|
|
+const handleMonthChange = async () => {
|
|
|
+ const monthResult = await getElectricMonth(currentElectricNumber.value, chartData.year, chartData.month)
|
|
|
+ if (monthResult) {
|
|
|
+ chartData.monthData.total = monthResult.allMonthData
|
|
|
+ chartData.monthData.dailyData = monthResult.meterDayDataList
|
|
|
+ await nextTick()
|
|
|
+ initMonthChart()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 其他原有函数保持不变
|
|
|
+const add = async (item: { companyName: string, electricNumber: string }) => {
|
|
|
+ const res = await clientPost<{
|
|
|
+ companyName: string,
|
|
|
+ electricNumber: string
|
|
|
+ }, BaseResponse>('/infrared/companyElectric/save', {
|
|
|
+ companyName: item.companyName,
|
|
|
+ electricNumber: item.electricNumber
|
|
|
+ })
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ ElMessage.success('新增成功')
|
|
|
+ getList()
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+const edit = async (item: { id: number, companyName: string, electricNumber: string }) => {
|
|
|
+ const res = await clientPost<{
|
|
|
+ id: number,
|
|
|
+ companyName: string,
|
|
|
+ electricNumber: string
|
|
|
+ }, BaseResponse>('/infrared/companyElectric/update', {
|
|
|
+ id: item.id,
|
|
|
+ companyName: item.companyName,
|
|
|
+ electricNumber: item.electricNumber
|
|
|
+ })
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ ElMessage.success('修改成功')
|
|
|
+ getList()
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+const deleteBatch = async (ids: string[]) => {
|
|
|
+ const res = await clientPost<string, BaseResponse>('/infrared/companyElectric/deleteBatch', JSON.stringify(ids))
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ ElMessage.success('删除成功')
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+const getById = async (id: number) => {
|
|
|
+ const res = await clientGet<null, ElectricResponse>('/infrared/companyElectric/getById/' + id)
|
|
|
+ if (res.code !== 200) {
|
|
|
+ ElMessage.error(res.msg)
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+const handleSearch = () => {
|
|
|
+ pagination.pageNum = 1
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+const handleReset = () => {
|
|
|
+ Object.assign(searchForm, {
|
|
|
+ companyName: '',
|
|
|
+ electricNumber: ''
|
|
|
+ })
|
|
|
+ pagination.pageNum = 1
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+const handleAdd = () => {
|
|
|
+ dialogTitle.value = '新增电表'
|
|
|
+ isEdit.value = false
|
|
|
+ Object.assign(formData, {
|
|
|
+ id: 0,
|
|
|
+ companyName: '',
|
|
|
+ electricNumber: ''
|
|
|
+ })
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const handleEdit = async (row: ElectricItem) => {
|
|
|
+ dialogTitle.value = '编辑电表'
|
|
|
+ isEdit.value = true
|
|
|
+ const data = await getById(row.id)
|
|
|
+ if (data) {
|
|
|
+ Object.assign(formData, data)
|
|
|
+ dialogVisible.value = true
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleDelete = (row: ElectricItem) => {
|
|
|
+ ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ deleteBatch([row.id.toString()])
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const handleBatchDelete = () => {
|
|
|
+ if (selectedIds.value.length === 0) {
|
|
|
+ ElMessage.warning('请选择要删除的记录')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ ElMessageBox.confirm(`确定要删除选中的 ${selectedIds.value.length} 条记录吗?`, '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ deleteBatch(selectedIds.value.map(id => id.toString()))
|
|
|
+ selectedIds.value = []
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const handleSelectionChange = (selection: ElectricItem[]) => {
|
|
|
+ selectedIds.value = selection.map(item => item.id)
|
|
|
+}
|
|
|
+
|
|
|
+const handlePageChange = (page: number) => {
|
|
|
+ pagination.pageNum = page
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+const handleSizeChange = (size: number) => {
|
|
|
+ pagination.pageSize = size
|
|
|
+ pagination.pageNum = 1
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+const handleSubmit = async () => {
|
|
|
+ if (!formRef.value) return
|
|
|
+
|
|
|
+ try {
|
|
|
+ await formRef.value.validate()
|
|
|
+ let success = false
|
|
|
+
|
|
|
+ if (isEdit.value) {
|
|
|
+ success = await edit(formData)
|
|
|
+ } else {
|
|
|
+ success = await add(formData)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (success) {
|
|
|
+ dialogVisible.value = false
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('表单验证失败', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ getList()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="p-6 bg-gray-50 min-h-screen">
|
|
|
+ <!-- 页面标题 -->
|
|
|
+ <div class="mb-6">
|
|
|
+ <h1 class="text-2xl font-bold text-gray-800 mb-2">智慧电表总览</h1>
|
|
|
+ <p class="text-gray-600">管理公司电表信息</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 搜索区域 -->
|
|
|
+ <div class="bg-white p-4 rounded-lg shadow-sm mb-4">
|
|
|
+ <el-form :model="searchForm" inline class="search-form">
|
|
|
+ <el-form-item label="公司名称">
|
|
|
+ <el-input
|
|
|
+ v-model="searchForm.companyName"
|
|
|
+ placeholder="请输入公司名称"
|
|
|
+ clearable
|
|
|
+ class="w-200px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="电表编号">
|
|
|
+ <el-input
|
|
|
+ v-model="searchForm.electricNumber"
|
|
|
+ placeholder="请输入电表编号"
|
|
|
+ clearable
|
|
|
+ class="w-200px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="handleSearch">
|
|
|
+ <Search class="w-4 h-4 mr-1" />
|
|
|
+ 搜索
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="handleReset">
|
|
|
+ <RefreshCw class="w-4 h-4 mr-1" />
|
|
|
+ 重置
|
|
|
+ </el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 操作区域 -->
|
|
|
+ <div class="bg-white p-4 rounded-lg shadow-sm">
|
|
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <el-button type="primary" @click="handleAdd">
|
|
|
+ <Plus class="w-4 h-4 mr-1" />
|
|
|
+ 新增电表
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ :disabled="selectedIds.length === 0"
|
|
|
+ @click="handleBatchDelete"
|
|
|
+ >
|
|
|
+ <Delete class="w-4 h-4 mr-1" />
|
|
|
+ 批量删除
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-500">
|
|
|
+ 共 {{ pagination.total }} 条记录
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 数据表格 -->
|
|
|
+ <el-table
|
|
|
+ :data="tableData"
|
|
|
+ :loading="loading"
|
|
|
+ @selection-change="handleSelectionChange"
|
|
|
+ stripe
|
|
|
+ class="w-full"
|
|
|
+ >
|
|
|
+ <el-table-column type="selection" width="55" />
|
|
|
+ <el-table-column prop="id" label="ID" width="80" />
|
|
|
+ <el-table-column prop="companyName" label="公司名称" min-width="200" />
|
|
|
+ <el-table-column prop="electricNumber" label="电表编号" min-width="150">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ text
|
|
|
+ @click="handleShowChart(row)"
|
|
|
+ class="p-0 h-auto font-normal"
|
|
|
+ >
|
|
|
+ <BarChart3 class="w-4 h-4 mr-1" />
|
|
|
+ {{ row.electricNumber }}
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="createTime" label="创建时间" width="180">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ row.createTime ? new Date(row.createTime).toLocaleString() : '-' }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="150" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ text
|
|
|
+ @click="handleEdit(row)"
|
|
|
+ >
|
|
|
+ <Edit class="w-3 h-3 mr-1" />
|
|
|
+ 编辑
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ size="small"
|
|
|
+ text
|
|
|
+ @click="handleDelete(row)"
|
|
|
+ >
|
|
|
+ <Delete class="w-3 h-3 mr-1" />
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <div class="flex justify-center mt-4">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="pagination.pageNum"
|
|
|
+ v-model:page-size="pagination.pageSize"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
+ :total="pagination.total"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ @current-change="handlePageChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 新增/编辑弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="dialogVisible"
|
|
|
+ :title="dialogTitle"
|
|
|
+ width="500px"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ >
|
|
|
+ <el-form
|
|
|
+ :model="formData"
|
|
|
+ :rules="formRules"
|
|
|
+ ref="formRef"
|
|
|
+ label-width="100px"
|
|
|
+ class="dialog-form"
|
|
|
+ >
|
|
|
+ <el-form-item label="公司名称" prop="companyName">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.companyName"
|
|
|
+ placeholder="请输入公司名称"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="电表编号" prop="electricNumber">
|
|
|
+ <el-input
|
|
|
+ v-model="formData.electricNumber"
|
|
|
+ placeholder="请输入电表编号"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleSubmit">
|
|
|
+ {{ isEdit ? '更新' : '新增' }}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 图表全屏对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="chartDialogVisible"
|
|
|
+ :title="`${currentCompanyName} - ${currentElectricNumber} 用电情况分析`"
|
|
|
+ fullscreen
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ >
|
|
|
+ <div v-loading="chartLoading" class="chart-container">
|
|
|
+ <!-- 控制面板 -->
|
|
|
+ <div class="flex items-center justify-between mb-6 p-4 bg-gray-50 rounded-lg">
|
|
|
+ <div class="flex items-center gap-4">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <Calendar class="w-4 h-4" />
|
|
|
+ <span>年份:</span>
|
|
|
+ <el-select v-model="chartData.year" @change="handleYearChange" class="w-32">
|
|
|
+ <el-option
|
|
|
+ v-for="year in [2021, 2022, 2023, 2024, 2025]"
|
|
|
+ :key="year"
|
|
|
+ :label="year"
|
|
|
+ :value="year"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <Clock class="w-4 h-4" />
|
|
|
+ <span>月份:</span>
|
|
|
+ <el-select v-model="chartData.month" @change="handleMonthChange" class="w-32">
|
|
|
+ <el-option
|
|
|
+ v-for="month in 12"
|
|
|
+ :key="month"
|
|
|
+ :label="`${month}月`"
|
|
|
+ :value="month"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-600">
|
|
|
+ 电表编号: {{ currentElectricNumber }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 今日用电量卡片区域 -->
|
|
|
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
|
|
+ <!-- 今日用电量主卡片 -->
|
|
|
+ <el-card class="today-power-card" shadow="hover">
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <div class="p-2 rounded-lg bg-blue-100">
|
|
|
+ <Zap class="w-5 h-5 text-blue-600" />
|
|
|
+ </div>
|
|
|
+ <span class="font-semibold text-gray-700">今日用电量</span>
|
|
|
+ </div>
|
|
|
+ <el-tag
|
|
|
+ :type="getPowerLevel(chartData.dayData.total).level === 'low' ? 'success' : getPowerLevel(chartData.dayData.total).level === 'medium' ? 'warning' : 'danger'"
|
|
|
+ size="small">
|
|
|
+ {{ getPowerLevel(chartData.dayData.total).text }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="text-center py-4">
|
|
|
+ <div class="text-4xl font-bold mb-2" :style="{ color: getPowerLevel(chartData.dayData.total).color }">
|
|
|
+ {{ chartData.dayData.total }}
|
|
|
+ </div>
|
|
|
+ <div class="text-lg text-gray-500 mb-4">kWh</div>
|
|
|
+ <div class="flex items-center justify-center gap-2 text-sm text-gray-600">
|
|
|
+ <Activity class="w-4 h-4" />
|
|
|
+ <span>实时监控</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 月度对比卡片 -->
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <div class="p-2 rounded-lg bg-green-100">
|
|
|
+ <TrendingUp class="w-5 h-5 text-green-600" />
|
|
|
+ </div>
|
|
|
+ <span class="font-semibold text-gray-700">本月累计</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="py-4">
|
|
|
+ <div class="text-2xl font-bold text-green-600 mb-2">
|
|
|
+ {{ chartData.monthData.total }}
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-500 mb-3">kWh</div>
|
|
|
+ <div class="flex items-center justify-between text-xs">
|
|
|
+ <span class="text-gray-600">日均用电</span>
|
|
|
+ <span class="font-medium">{{ (chartData.monthData.total / new Date().getDate()).toFixed(1) }} kWh</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 年度对比卡片 -->
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <div class="p-2 rounded-lg bg-purple-100">
|
|
|
+ <Power class="w-5 h-5 text-purple-600" />
|
|
|
+ </div>
|
|
|
+ <span class="font-semibold text-gray-700">年度累计</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="py-4">
|
|
|
+ <div class="text-2xl font-bold text-purple-600 mb-2">
|
|
|
+ {{ chartData.yearData.total }}
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-500 mb-3">kWh</div>
|
|
|
+ <div class="flex items-center justify-between text-xs">
|
|
|
+ <span class="text-gray-600">月均用电</span>
|
|
|
+ <span class="font-medium">{{ (chartData.yearData.total / 12).toFixed(1) }} kWh</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 用电量趋势分析卡片 -->
|
|
|
+ <el-card class="mb-6" shadow="hover">
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <div class="p-2 rounded-lg bg-orange-100">
|
|
|
+ <BarChart3 class="w-5 h-5 text-orange-600" />
|
|
|
+ </div>
|
|
|
+ <span class="font-semibold text-gray-700">
|
|
|
+ 用电量趋势分析
|
|
|
+ <el-tooltip>
|
|
|
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
|
|
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|
|
+ class="lucide lucide-shield-question-mark-icon lucide-shield-question-mark"><path
|
|
|
+ d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /><path
|
|
|
+ d="M9.1 9a3 3 0 0 1 5.82 1c0 2-3 3-3 3" /><path d="M12 17h.01" /></svg>
|
|
|
+ <template #content>
|
|
|
+ 50KW/h以下为节能用电<br />
|
|
|
+ 50KW/h以上为正常<br />
|
|
|
+ 100KW/h以上为偏高<br />
|
|
|
+ 预计月度为当日用电量乘以30
|
|
|
+ </template>
|
|
|
+ </el-tooltip>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 py-4">
|
|
|
+ <div class="text-center p-4 bg-gray-50 rounded-lg">
|
|
|
+ <div class="text-lg font-semibold text-gray-700 mb-1">今日状态</div>
|
|
|
+ <div class="text-sm" :style="{ color: getPowerLevel(chartData.dayData.total).color }">
|
|
|
+ {{ getPowerLevel(chartData.dayData.total).text }}用电
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="text-center p-4 bg-gray-50 rounded-lg">
|
|
|
+ <div class="text-lg font-semibold text-gray-700 mb-1">预计月度</div>
|
|
|
+ <div class="text-sm text-blue-600">
|
|
|
+ {{ (chartData.dayData.total * 30).toFixed(0) }} kWh
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="text-center p-4 bg-gray-50 rounded-lg">
|
|
|
+ <div class="text-lg font-semibold text-gray-700 mb-1">节能建议</div>
|
|
|
+ <div class="text-sm text-green-600">
|
|
|
+ {{ chartData.dayData.total < 50 ? '保持良好' : chartData.dayData.total < 100 ? '适度节能' : '加强管控'
|
|
|
+ }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 图表网格 -->
|
|
|
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
|
+ <!-- 年度用电量图表 -->
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <BarChart3 class="w-4 h-4 text-blue-600" />
|
|
|
+ <span class="font-semibold">年度用电量统计</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div ref="yearChartRef" class="w-full h-96"></div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 月度用电量图表 -->
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <TrendingUp class="w-4 h-4 text-green-600" />
|
|
|
+ <span class="font-semibold">月度用电量统计</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div ref="monthChartRef" class="w-full h-96"></div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button @click="chartDialogVisible = false">关闭</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.search-form .el-form-item {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-footer {
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-container {
|
|
|
+ min-height: 600px;
|
|
|
+}
|
|
|
+
|
|
|
+.today-power-card {
|
|
|
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+
|
|
|
+.today-power-card :deep(.el-card__body) {
|
|
|
+ padding: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+/* UnoCSS 样式补充 */
|
|
|
+.w-200px {
|
|
|
+ width: 200px;
|
|
|
+}
|
|
|
+
|
|
|
+.w-32 {
|
|
|
+ width: 8rem;
|
|
|
+}
|
|
|
+
|
|
|
+.h-96 {
|
|
|
+ height: 24rem;
|
|
|
+}
|
|
|
+
|
|
|
+.grid {
|
|
|
+ display: grid;
|
|
|
+}
|
|
|
+
|
|
|
+.grid-cols-1 {
|
|
|
+ grid-template-columns: repeat(1, minmax(0, 1fr));
|
|
|
+}
|
|
|
+
|
|
|
+.gap-6 {
|
|
|
+ gap: 1.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+.gap-4 {
|
|
|
+ gap: 1rem;
|
|
|
+}
|
|
|
+
|
|
|
+.gap-2 {
|
|
|
+ gap: 0.5rem;
|
|
|
+}
|
|
|
+
|
|
|
+@media (min-width: 768px) {
|
|
|
+ .md\:grid-cols-3 {
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (min-width: 1024px) {
|
|
|
+ .lg\:grid-cols-2 {
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+
|
|
|
+ .lg\:grid-cols-3 {
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+
|
|
|
+ .lg\:col-span-2 {
|
|
|
+ grid-column: span 2 / span 2;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|