Compare commits
129 Commits
@ -0,0 +1,239 @@ |
|||||||
|
import request from "@/utils/request"; |
||||||
|
import Cookies from 'js-cookie'; |
||||||
|
|
||||||
|
/** |
||||||
|
* 创建AI报表任务 |
||||||
|
* @param {string} prompt - 用户输入的提示词 |
||||||
|
* @returns {Promise<{taskId: string}>} |
||||||
|
*/ |
||||||
|
export function createReportTask(prompt) { |
||||||
|
return request({ |
||||||
|
url: "/ai/reports/async", |
||||||
|
method: "post", |
||||||
|
data: prompt, |
||||||
|
headers: { |
||||||
|
"Content-Type": "text/plain", |
||||||
|
}, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 下载AI报表(带token认证) |
||||||
|
* @param {string} downloadUrl - 下载URL |
||||||
|
* @returns {Promise<{ blob: Blob, fileName: string }>} |
||||||
|
*/ |
||||||
|
export function downloadReport(downloadUrl) { |
||||||
|
const token = Cookies.get("Admin-Token"); |
||||||
|
const baseURL = process.env.VUE_APP_BASE_API; |
||||||
|
const url = downloadUrl.startsWith('http') ? downloadUrl : `${baseURL}${downloadUrl}`; |
||||||
|
|
||||||
|
return fetch(url, { |
||||||
|
method: 'GET', |
||||||
|
headers: { |
||||||
|
'Authorization': `Bearer ${token}`, |
||||||
|
}, |
||||||
|
}).then(response => { |
||||||
|
if (!response.ok) { |
||||||
|
throw new Error(`Download failed with status: ${response.status}`); |
||||||
|
} |
||||||
|
|
||||||
|
// 解析Content-Disposition头获取文件名
|
||||||
|
let fileName = 'report.docx'; // 默认文件名
|
||||||
|
const contentDisposition = response.headers.get('Content-Disposition'); |
||||||
|
|
||||||
|
if (contentDisposition) { |
||||||
|
// 尝试解析 filename*=UTF-8'' 格式
|
||||||
|
const utf8FilenameMatch = contentDisposition.match(/filename\*=UTF-8''(.+)/); |
||||||
|
if (utf8FilenameMatch) { |
||||||
|
try { |
||||||
|
fileName = decodeURIComponent(utf8FilenameMatch[1]); |
||||||
|
} catch (e) { |
||||||
|
console.error('Failed to decode filename:', e); |
||||||
|
} |
||||||
|
} else { |
||||||
|
// 尝试解析 filename="..." 或 filename=... 格式
|
||||||
|
const filenameMatch = contentDisposition.match(/filename="?([^"]+)"?/); |
||||||
|
if (filenameMatch) { |
||||||
|
fileName = filenameMatch[1]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return response.blob().then(blob => ({ blob, fileName })); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 创建SSE连接(使用fetch实现,支持Authorization请求头) |
||||||
|
* @param {string} taskId - 任务ID |
||||||
|
* @param {Object} callbacks - 回调函数集合 |
||||||
|
* @param {Function} callbacks.onReasoning - 推理内容回调 |
||||||
|
* @param {Function} callbacks.onCompleted - 完成回调 |
||||||
|
* @param {Function} callbacks.onFailed - 失败回调 |
||||||
|
* @param {Function} callbacks.onError - 错误回调 |
||||||
|
* @param {number} timeout - 超时时间(毫秒),默认30000(30秒) |
||||||
|
* @returns {Object} SSE连接实例,包含close方法 |
||||||
|
*/ |
||||||
|
export function createSSEConnection(taskId, callbacks, timeout = 30000) { |
||||||
|
const { |
||||||
|
onReasoning = () => {}, |
||||||
|
onCompleted = () => {}, |
||||||
|
onFailed = () => {}, |
||||||
|
onError = () => {}, |
||||||
|
} = callbacks; |
||||||
|
|
||||||
|
const token = Cookies.get("Admin-Token"); |
||||||
|
const baseURL = process.env.VUE_APP_BASE_API; |
||||||
|
const url = `${baseURL}/ai/reports/async/${taskId}/subscribe`; |
||||||
|
|
||||||
|
let controller = new AbortController(); |
||||||
|
let timeoutId = null; |
||||||
|
let isCompleted = false; |
||||||
|
|
||||||
|
const connect = async () => { |
||||||
|
// 设置超时
|
||||||
|
timeoutId = setTimeout(() => { |
||||||
|
if (!isCompleted) { |
||||||
|
controller.abort(); |
||||||
|
const error = new Error(`SSE connection timeout after ${timeout}ms`); |
||||||
|
onError(error); |
||||||
|
} |
||||||
|
}, timeout); |
||||||
|
|
||||||
|
try { |
||||||
|
const response = await fetch(url, { |
||||||
|
method: 'GET', |
||||||
|
headers: { |
||||||
|
'Authorization': `Bearer ${token}`, |
||||||
|
'Accept': 'text/event-stream', |
||||||
|
}, |
||||||
|
signal: controller.signal, |
||||||
|
}); |
||||||
|
|
||||||
|
if (!response.ok) { |
||||||
|
throw new Error(`HTTP error! status: ${response.status}`); |
||||||
|
} |
||||||
|
|
||||||
|
const reader = response.body.getReader(); |
||||||
|
const decoder = new TextDecoder(); |
||||||
|
let buffer = ''; |
||||||
|
let lastDataTime = Date.now(); |
||||||
|
|
||||||
|
// 数据超时检测(如果超过超时时间没有收到新数据)
|
||||||
|
const dataTimeoutId = setInterval(() => { |
||||||
|
if (!isCompleted && Date.now() - lastDataTime > timeout) { |
||||||
|
clearInterval(dataTimeoutId); |
||||||
|
controller.abort(); |
||||||
|
const error = new Error(`SSE no data received for ${timeout}ms`); |
||||||
|
onError(error); |
||||||
|
} |
||||||
|
}, 5000); |
||||||
|
|
||||||
|
let currentEventType = null; |
||||||
|
let currentData = ''; |
||||||
|
|
||||||
|
while (true) { |
||||||
|
const { done, value } = await reader.read(); |
||||||
|
if (done) break; |
||||||
|
|
||||||
|
lastDataTime = Date.now(); |
||||||
|
buffer += decoder.decode(value, { stream: true }); |
||||||
|
const lines = buffer.split('\n'); |
||||||
|
buffer = lines.pop() || ''; |
||||||
|
|
||||||
|
for (const line of lines) { |
||||||
|
const trimmedLine = line.trim(); |
||||||
|
if (trimmedLine.startsWith('event:')) { |
||||||
|
currentEventType = trimmedLine.slice(6).trim(); |
||||||
|
} else if (trimmedLine.startsWith('data:')) { |
||||||
|
currentData = trimmedLine.slice(5).trim(); |
||||||
|
if (currentEventType && currentData) { |
||||||
|
if (currentEventType.toLowerCase() === 'reasoning') { |
||||||
|
// reasoning 数据通常是文本内容,直接传递
|
||||||
|
onReasoning(currentData); |
||||||
|
} else if (currentEventType.toLowerCase() === 'completed') { |
||||||
|
// COMPLETED 数据可能是JSON对象,尝试解析
|
||||||
|
try { |
||||||
|
const parsedData = JSON.parse(currentData); |
||||||
|
onCompleted(parsedData); |
||||||
|
} catch (e) { |
||||||
|
onCompleted(currentData); |
||||||
|
} |
||||||
|
isCompleted = true; |
||||||
|
clearTimeout(timeoutId); |
||||||
|
clearInterval(dataTimeoutId); |
||||||
|
} else if (currentEventType.toLowerCase() === 'failed') { |
||||||
|
try { |
||||||
|
const parsedData = JSON.parse(currentData); |
||||||
|
onFailed(parsedData); |
||||||
|
} catch (e) { |
||||||
|
onFailed(currentData); |
||||||
|
} |
||||||
|
isCompleted = true; |
||||||
|
clearTimeout(timeoutId); |
||||||
|
clearInterval(dataTimeoutId); |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (trimmedLine === '') { |
||||||
|
// 空行,事件分隔符,重置事件和数据
|
||||||
|
currentEventType = null; |
||||||
|
currentData = ''; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (error) { |
||||||
|
if (error.name === 'AbortError') { |
||||||
|
if (!isCompleted) { |
||||||
|
console.log('SSE connection closed'); |
||||||
|
} |
||||||
|
} else { |
||||||
|
console.error('SSE error:', error); |
||||||
|
onError(error); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
clearTimeout(timeoutId); |
||||||
|
isCompleted = true; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
connect(); |
||||||
|
|
||||||
|
return { |
||||||
|
close: () => { |
||||||
|
clearTimeout(timeoutId); |
||||||
|
isCompleted = true; |
||||||
|
controller.abort(); |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取 AI报表数据(用于前端生成报表) |
||||||
|
* @param {string} reportType - 报表类型:comprehensive(综合), air conditioning(空调), lighting(照明), pump(水泵) |
||||||
|
* @param {Object} params - 查询参数 |
||||||
|
* @returns {Promise} |
||||||
|
*/ |
||||||
|
export function getReportData(reportType, params) { |
||||||
|
return request({ |
||||||
|
url: `/ai/reports/${reportType}/data`, |
||||||
|
method: "get", |
||||||
|
params: params, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* AI 对话接口(实时交互) |
||||||
|
* @param {string} message - 用户消息 |
||||||
|
* @param {string} conversationId - 会话 ID |
||||||
|
* @returns {Promise} |
||||||
|
*/ |
||||||
|
export function chat(message, conversationId) { |
||||||
|
return request({ |
||||||
|
url: "/ai/chat", |
||||||
|
method: "post", |
||||||
|
data: { |
||||||
|
message, |
||||||
|
conversationId, |
||||||
|
}, |
||||||
|
}); |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
import request from "@/utils/request"; |
||||||
|
|
||||||
|
// 查询运行记录
|
||||||
|
export function boilerSysList(data) { |
||||||
|
return request({ |
||||||
|
url: "/reportSteam/list", |
||||||
|
method: "post", |
||||||
|
data, |
||||||
|
}); |
||||||
|
} |
||||||
|
// 编辑运行记录
|
||||||
|
export function boilerSysEdit(data) { |
||||||
|
return request({ |
||||||
|
url: "/reportSteam/edit", |
||||||
|
method: "put", |
||||||
|
data, |
||||||
|
}); |
||||||
|
} |
||||||
|
// 导出
|
||||||
|
export function boilerSysExport(data) { |
||||||
|
return request({ |
||||||
|
url: "/reportSteam/export", |
||||||
|
method: "post", |
||||||
|
data, |
||||||
|
responseType: "blob", |
||||||
|
}); |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
import request from "@/utils/request"; |
||||||
|
|
||||||
|
// 采暖泵列表
|
||||||
|
export function heatPumpList(query) { |
||||||
|
return request({ |
||||||
|
url: "/device/heatPump/list", |
||||||
|
method: "get", |
||||||
|
params: query, |
||||||
|
}); |
||||||
|
} |
||||||
|
// 采暖泵在线情况
|
||||||
|
export function heatPumpOnlineList(query) { |
||||||
|
return request({ |
||||||
|
url: "/device/heatPump/online", |
||||||
|
method: "get", |
||||||
|
params: query, |
||||||
|
}); |
||||||
|
} |
||||||
|
// 采暖泵报警列表
|
||||||
|
export function heatPumpAlarmList(query) { |
||||||
|
return request({ |
||||||
|
url: "/device/heatPump/alarmList", |
||||||
|
method: "get", |
||||||
|
params: query, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,27 @@ |
|||||||
|
import request from "@/utils/request"; |
||||||
|
|
||||||
|
// 查询运行记录
|
||||||
|
export function boilerSysList(data) { |
||||||
|
return request({ |
||||||
|
url: "/reportHeating/list", |
||||||
|
method: "post", |
||||||
|
data, |
||||||
|
}); |
||||||
|
} |
||||||
|
// 编辑运行记录
|
||||||
|
export function boilerSysEdit(data) { |
||||||
|
return request({ |
||||||
|
url: "/reportHeating/edit", |
||||||
|
method: "put", |
||||||
|
data, |
||||||
|
}); |
||||||
|
} |
||||||
|
// 导出
|
||||||
|
export function boilerSysExport(data) { |
||||||
|
return request({ |
||||||
|
url: "/reportHeating/export", |
||||||
|
method: "post", |
||||||
|
data, |
||||||
|
responseType: "blob", |
||||||
|
}); |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
import request from "@/utils/request"; |
||||||
|
|
||||||
|
// 热水锅炉设备参数列表
|
||||||
|
export function hotWaterBoiler(query) { |
||||||
|
return request({ |
||||||
|
url: "/device/hotWaterBoiler/list", |
||||||
|
method: "get", |
||||||
|
params: query, |
||||||
|
}); |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
import request from "@/utils/request"; |
||||||
|
|
||||||
|
// 蒸汽锅炉设备参数列表
|
||||||
|
export function steamBoilerBoiler(query) { |
||||||
|
return request({ |
||||||
|
url: "/device/steamBoiler/list", |
||||||
|
method: "get", |
||||||
|
params: query, |
||||||
|
}); |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
import request from "@/utils/request"; |
||||||
|
|
||||||
|
// 查询运行记录
|
||||||
|
export function reportSysList(data) { |
||||||
|
return request({ |
||||||
|
url: "/reportSys/list", |
||||||
|
method: "post", |
||||||
|
data, |
||||||
|
}); |
||||||
|
} |
||||||
|
// 编辑运行记录
|
||||||
|
export function reportSysEdit(data) { |
||||||
|
return request({ |
||||||
|
url: "/reportSys/edit", |
||||||
|
method: "put", |
||||||
|
data, |
||||||
|
}); |
||||||
|
} |
||||||
|
// 导出
|
||||||
|
export function reportSysExport(data) { |
||||||
|
return request({ |
||||||
|
url: "/reportSys/export", |
||||||
|
method: "post", |
||||||
|
data, |
||||||
|
responseType: 'blob', |
||||||
|
}); |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
import request from "@/utils/request"; |
||||||
|
|
||||||
|
export const hotWaterList = (data) => { |
||||||
|
return request({ |
||||||
|
url: "/reportHotWater/list", |
||||||
|
method: "post", |
||||||
|
data: data, |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
// 导出
|
||||||
|
export const hotWaterExport = (data) => { |
||||||
|
return request({ |
||||||
|
url: "/reportHotWater/export", |
||||||
|
method: "post", |
||||||
|
data, |
||||||
|
responseType: "blob", |
||||||
|
}); |
||||||
|
}; |
||||||
|
// 修改
|
||||||
|
export const hotWaterEdit = (data) => { |
||||||
|
return request({ |
||||||
|
url: "/reportHotWater/edit", |
||||||
|
method: "put", |
||||||
|
data: data, |
||||||
|
}); |
||||||
|
}; |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
import request from "@/utils/request"; |
||||||
|
|
||||||
|
export const meterReadingsList = (data) => { |
||||||
|
return request({ |
||||||
|
url: "/reportMeterReadings/list", |
||||||
|
method: "post", |
||||||
|
data: data, |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
// 导出
|
||||||
|
export const meterReadingsExport = (data) => { |
||||||
|
return request({ |
||||||
|
url: "/reportMeterReadings/export", |
||||||
|
method: "post", |
||||||
|
data, |
||||||
|
responseType: "blob", |
||||||
|
}); |
||||||
|
}; |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
import request from "@/utils/request"; |
||||||
|
|
||||||
|
export const compreReport = (data) => { |
||||||
|
return request({ |
||||||
|
url: "/compre/report", |
||||||
|
method: "post", |
||||||
|
data, |
||||||
|
}); |
||||||
|
}; |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
import request from "@/utils/request"; |
||||||
|
|
||||||
|
// 工艺流程图数据列表
|
||||||
|
export function monitorList(query) { |
||||||
|
return request({ |
||||||
|
url: "/device/ers/monitor/list", |
||||||
|
method: "get", |
||||||
|
params: query, |
||||||
|
}); |
||||||
|
} |
||||||
|
// 累积热量框数据
|
||||||
|
export function monitorTotalDatas(query) { |
||||||
|
return request({ |
||||||
|
url: "/device/ers/monitor/totalDatas", |
||||||
|
method: "get", |
||||||
|
params: query, |
||||||
|
}); |
||||||
|
} |
||||||
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 468 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 576 KiB After Width: | Height: | Size: 576 KiB |
|
After Width: | Height: | Size: 738 KiB |
|
After Width: | Height: | Size: 340 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 531 KiB |
|
Before Width: | Height: | Size: 531 KiB After Width: | Height: | Size: 307 KiB |
|
After Width: | Height: | Size: 5.2 MiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 13 MiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.4 MiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 46 KiB |
@ -1,63 +1,87 @@ |
|||||||
import router from './router' |
import router from "./router"; |
||||||
import store from './store' |
import store from "./store"; |
||||||
import { Message } from 'element-ui' |
import { Message } from "element-ui"; |
||||||
import NProgress from 'nprogress' |
import NProgress from "nprogress"; |
||||||
import 'nprogress/nprogress.css' |
import "nprogress/nprogress.css"; |
||||||
import { getToken } from '@/utils/auth' |
import { getToken } from "@/utils/auth"; |
||||||
import { isPathMatch } from '@/utils/validate' |
import { isPathMatch } from "@/utils/validate"; |
||||||
import { isRelogin } from '@/utils/request' |
import { isRelogin } from "@/utils/request"; |
||||||
|
import { |
||||||
|
isFullscreenSupported, |
||||||
|
requestFullscreen, |
||||||
|
isFullscreen, |
||||||
|
} from "@/utils/fullscreen"; |
||||||
|
NProgress.configure({ showSpinner: false }); |
||||||
|
|
||||||
NProgress.configure({ showSpinner: false }) |
const whiteList = ["/login", "/register"]; |
||||||
|
|
||||||
const whiteList = ['/login', '/register'] |
|
||||||
|
|
||||||
const isWhiteList = (path) => { |
const isWhiteList = (path) => { |
||||||
return whiteList.some(pattern => isPathMatch(pattern, path)) |
return whiteList.some((pattern) => isPathMatch(pattern, path)); |
||||||
} |
}; |
||||||
|
|
||||||
|
let userManuallyExitedFullscreen = false; |
||||||
|
|
||||||
|
// 监听全屏状态变化事件
|
||||||
|
document.addEventListener("fullscreenchange", () => { |
||||||
|
if (!isFullscreen()) { |
||||||
|
userManuallyExitedFullscreen = true; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
router.beforeEach((to, from, next) => { |
router.beforeEach((to, from, next) => { |
||||||
NProgress.start() |
NProgress.start(); |
||||||
if (getToken()) { |
if (getToken()) { |
||||||
to.meta.title && store.dispatch('settings/setTitle', to.meta.title) |
to.meta.title && store.dispatch("settings/setTitle", to.meta.title); |
||||||
/* has token*/ |
/* has token*/ |
||||||
if (to.path === '/login') { |
if (to.path === "/login") { |
||||||
next({ path: '/' }) |
next({ path: "/" }); |
||||||
NProgress.done() |
NProgress.done(); |
||||||
} else if (isWhiteList(to.path)) { |
} else if (isWhiteList(to.path)) { |
||||||
next() |
if (isFullscreenSupported() && !userManuallyExitedFullscreen) { |
||||||
|
const element = document.documentElement; |
||||||
|
requestFullscreen(element); |
||||||
|
} |
||||||
|
next(); |
||||||
} else { |
} else { |
||||||
if (store.getters.roles.length === 0) { |
if (store.getters.roles.length === 0) { |
||||||
isRelogin.show = true |
isRelogin.show = true; |
||||||
// 判断当前用户是否已拉取完user_info信息
|
// 判断当前用户是否已拉取完user_info信息
|
||||||
store.dispatch('GetInfo').then(() => { |
store |
||||||
isRelogin.show = false |
.dispatch("GetInfo") |
||||||
store.dispatch('GenerateRoutes').then(accessRoutes => { |
.then(() => { |
||||||
// 根据roles权限生成可访问的路由表
|
isRelogin.show = false; |
||||||
router.addRoutes(accessRoutes) // 动态添加可访问路由表
|
store.dispatch("GenerateRoutes").then((accessRoutes) => { |
||||||
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
|
// 根据roles权限生成可访问的路由表
|
||||||
}) |
router.addRoutes(accessRoutes); // 动态添加可访问路由表
|
||||||
}).catch(err => { |
next({ ...to, replace: true }); // hack方法 确保addRoutes已完成
|
||||||
store.dispatch('LogOut').then(() => { |
}); |
||||||
Message.error(err) |
|
||||||
next({ path: '/' }) |
|
||||||
}) |
|
||||||
}) |
}) |
||||||
|
.catch((err) => { |
||||||
|
store.dispatch("LogOut").then(() => { |
||||||
|
Message.error(err); |
||||||
|
next({ path: "/" }); |
||||||
|
}); |
||||||
|
}); |
||||||
} else { |
} else { |
||||||
next() |
if (isFullscreenSupported() && !userManuallyExitedFullscreen) { |
||||||
|
const element = document.documentElement; |
||||||
|
requestFullscreen(element); |
||||||
|
} |
||||||
|
next(); |
||||||
} |
} |
||||||
} |
} |
||||||
} else { |
} else { |
||||||
// 没有token
|
// 没有token
|
||||||
if (isWhiteList(to.path)) { |
if (isWhiteList(to.path)) { |
||||||
// 在免登录白名单,直接进入
|
// 在免登录白名单,直接进入
|
||||||
next() |
next(); |
||||||
} else { |
} else { |
||||||
next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) // 否则全部重定向到登录页
|
next(`/login?redirect=${encodeURIComponent(to.fullPath)}`); // 否则全部重定向到登录页
|
||||||
NProgress.done() |
NProgress.done(); |
||||||
} |
} |
||||||
} |
} |
||||||
}) |
}); |
||||||
|
|
||||||
router.afterEach(() => { |
router.afterEach(() => { |
||||||
NProgress.done() |
NProgress.done(); |
||||||
}) |
}); |
||||||
|
|||||||
@ -0,0 +1,53 @@ |
|||||||
|
// 检查浏览器是否支持全屏 API
|
||||||
|
function isFullscreenSupported() { |
||||||
|
return ( |
||||||
|
document.fullscreenEnabled || |
||||||
|
document.webkitFullscreenEnabled || |
||||||
|
document.mozFullScreenEnabled || |
||||||
|
document.msFullscreenEnabled |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// 进入全屏模式
|
||||||
|
function requestFullscreen(element) { |
||||||
|
if (element.requestFullscreen) { |
||||||
|
element.requestFullscreen(); |
||||||
|
} else if (element.webkitRequestFullscreen) { |
||||||
|
element.webkitRequestFullscreen(); |
||||||
|
} else if (element.mozRequestFullScreen) { |
||||||
|
element.mozRequestFullScreen(); |
||||||
|
} else if (element.msRequestFullscreen) { |
||||||
|
element.msRequestFullscreen(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 退出全屏模式
|
||||||
|
function exitFullscreen() { |
||||||
|
if (document.exitFullscreen) { |
||||||
|
document.exitFullscreen(); |
||||||
|
} else if (document.webkitExitFullscreen) { |
||||||
|
document.webkitExitFullscreen(); |
||||||
|
} else if (document.mozCancelFullScreen) { |
||||||
|
document.mozCancelFullScreen(); |
||||||
|
} else if (document.msExitFullscreen) { |
||||||
|
document.msExitFullscreen(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 检查当前是否处于全屏状态
|
||||||
|
function isFullscreen() { |
||||||
|
return ( |
||||||
|
document.fullscreenElement || |
||||||
|
document.webkitFullscreenElement || |
||||||
|
document.mozFullScreenElement || |
||||||
|
document.msFullscreenElement |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
export { |
||||||
|
isFullscreenSupported, |
||||||
|
requestFullscreen, |
||||||
|
exitFullscreen, |
||||||
|
isFullscreen, |
||||||
|
}; |
||||||
@ -0,0 +1,381 @@ |
|||||||
|
# AI 智能报表组件化开发文档 |
||||||
|
|
||||||
|
## 📦 组件架构 |
||||||
|
|
||||||
|
采用组件化设计思路,将每个报表类型独立封装为可复用的 Vue 组件,提高代码的可维护性和可扩展性。 |
||||||
|
|
||||||
|
### 目录结构 |
||||||
|
|
||||||
|
``` |
||||||
|
src/views/ai/ |
||||||
|
├── components/ # 报表组件目录 |
||||||
|
│ ├── ComprehensiveReport.vue # 项目综合分析报告组件 |
||||||
|
│ ├── AirConditioningReport.vue # 空调制冷系统分析报告组件 |
||||||
|
│ ├── LightingReport.vue # 照明系统分析报告组件 |
||||||
|
│ ├── PumpReport.vue # 水泵系统分析报告组件 |
||||||
|
│ └── index.js # 组件统一导出文件 |
||||||
|
├── index.vue # AI报表主页面 |
||||||
|
└── README.md # 功能说明文档 |
||||||
|
``` |
||||||
|
|
||||||
|
## 🎯 组件说明 |
||||||
|
|
||||||
|
### 1. ComprehensiveReport (项目综合分析报告) |
||||||
|
|
||||||
|
**功能描述**: 综合展示项目所有子系统的设备配置、运行数据和能效表现 |
||||||
|
|
||||||
|
**包含图表**: |
||||||
|
- 各系统能耗占比饼图 |
||||||
|
- 系统能效趋势折线图 |
||||||
|
- 主要设备用电对比柱状图 |
||||||
|
|
||||||
|
**使用示例**: |
||||||
|
```vue |
||||||
|
<ComprehensiveReport |
||||||
|
:reportData="reportData" |
||||||
|
:userName="userName" |
||||||
|
/> |
||||||
|
``` |
||||||
|
|
||||||
|
### 2. AirConditioningReport (空调制冷系统分析报告) |
||||||
|
|
||||||
|
**功能描述**: 针对空调制冷系统的专业分析报告 |
||||||
|
|
||||||
|
**包含图表**: |
||||||
|
- 制冷设备组用电占比饼图 |
||||||
|
- 制冷系统能效趋势图 |
||||||
|
- 每日用电量与制冷量对比图 |
||||||
|
|
||||||
|
**使用示例**: |
||||||
|
```vue |
||||||
|
<AirConditioningReport |
||||||
|
:reportData="reportData" |
||||||
|
:userName="userName" |
||||||
|
/> |
||||||
|
``` |
||||||
|
|
||||||
|
### 3. LightingReport (照明系统分析报告) |
||||||
|
|
||||||
|
**功能描述**: 照明系统节能效果分析报告 |
||||||
|
|
||||||
|
**包含图表**: |
||||||
|
- 节电量趋势图 (双维度:节能数 + 节电量) |
||||||
|
- 节能率趋势图 (含环比/同比分析) |
||||||
|
|
||||||
|
**使用示例**: |
||||||
|
```vue |
||||||
|
<LightingReport |
||||||
|
:reportData="reportData" |
||||||
|
:userName="userName" |
||||||
|
/> |
||||||
|
``` |
||||||
|
|
||||||
|
### 4. PumpReport (水泵系统分析报告) |
||||||
|
|
||||||
|
**功能描述**: 水泵系统运行效率和能耗分析报告 |
||||||
|
|
||||||
|
**包含图表**: |
||||||
|
- 水泵组用电占比饼图 |
||||||
|
- 分区吨水能耗趋势图 |
||||||
|
- 每日供水量与用电量对比图 |
||||||
|
|
||||||
|
**使用示例**: |
||||||
|
```vue |
||||||
|
<PumpReport |
||||||
|
:reportData="reportData" |
||||||
|
:userName="userName" |
||||||
|
/> |
||||||
|
``` |
||||||
|
|
||||||
|
## 🔧 组件通用接口 |
||||||
|
|
||||||
|
### Props |
||||||
|
|
||||||
|
所有报表组件都接收以下 props: |
||||||
|
|
||||||
|
| Prop 名称 | 类型 | 必填 | 说明 | |
||||||
|
|--------------|--------|------|------------------------| |
||||||
|
| `reportData` | Object | 是 | 报表数据对象 | |
||||||
|
| `userName` | String | 否 | 操作员姓名,用于报表底部 | |
||||||
|
|
||||||
|
### reportData 数据结构 |
||||||
|
|
||||||
|
```javascript |
||||||
|
{ |
||||||
|
title: String, // 报表标题 |
||||||
|
generateTime: String, // 生成时间 |
||||||
|
summary: String, // 报告摘要 |
||||||
|
projectInfo: { // 项目情况 |
||||||
|
description: String, // 项目描述 |
||||||
|
configTable: Array, // 配置表格数据 |
||||||
|
configTableColumns: Array // 表格列定义 |
||||||
|
}, |
||||||
|
equipmentOverview: { // 设备概述 |
||||||
|
description: String // 设备描述 |
||||||
|
}, |
||||||
|
operationData: { // 运行数据 |
||||||
|
dataTable: Array, // 数据表格 |
||||||
|
tableColumns: Array // 表格列定义 |
||||||
|
}, |
||||||
|
analysisSummary: { // 分析总结 |
||||||
|
points: Array, // 分析要点 |
||||||
|
suggestions: Array // 优化建议 |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## 🎨 样式特点 |
||||||
|
|
||||||
|
### 统一风格 |
||||||
|
- 所有组件使用相同的样式基类 `.report-wrapper` |
||||||
|
- 保持与 Element UI 一致的设计语言 |
||||||
|
- 响应式布局,自适应不同屏幕尺寸 |
||||||
|
|
||||||
|
### 图表样式 |
||||||
|
- 图表容器固定高度 300px |
||||||
|
- 使用 grid 布局自动排列 |
||||||
|
- 最小宽度 400px,保证图表清晰度 |
||||||
|
|
||||||
|
### 配色方案 |
||||||
|
- 主色调:#409EFF (Element UI 主题色) |
||||||
|
- 成功色:#67C23A |
||||||
|
- 警告色:#E6A23C |
||||||
|
- 危险色:#F56C6C |
||||||
|
|
||||||
|
## 📊 ECharts 图表配置 |
||||||
|
|
||||||
|
### 图表自适应 |
||||||
|
每个组件都在 `mounted` 钩子中初始化图表,在 `beforeDestroy` 钩子中销毁实例: |
||||||
|
|
||||||
|
```javascript |
||||||
|
mounted() { |
||||||
|
this.$nextTick(() => { |
||||||
|
this.renderCharts(); |
||||||
|
}); |
||||||
|
}, |
||||||
|
beforeDestroy() { |
||||||
|
this.chartInstances.forEach((chart) => { |
||||||
|
if (chart) chart.dispose(); |
||||||
|
}); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 响应式配置 |
||||||
|
图表标题字体大小根据容器宽度动态计算: |
||||||
|
|
||||||
|
```javascript |
||||||
|
const width = this.$refs.chartBox?.clientWidth || 400; |
||||||
|
const titleFontSize = width / 50; |
||||||
|
``` |
||||||
|
|
||||||
|
## 🔄 动态组件加载 |
||||||
|
|
||||||
|
主页面使用 Vue 的动态组件特性: |
||||||
|
|
||||||
|
```vue |
||||||
|
<component |
||||||
|
:is="reportComponent" |
||||||
|
:reportData="reportData" |
||||||
|
:userName="userName" |
||||||
|
/> |
||||||
|
``` |
||||||
|
|
||||||
|
通过 `reportComponent` 计算属性动态返回组件名称: |
||||||
|
|
||||||
|
```javascript |
||||||
|
computed: { |
||||||
|
reportComponent() { |
||||||
|
if (!this.currentReportType) return null; |
||||||
|
return this.reportTypeMap[this.currentReportType]; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## 🚀 使用方式 |
||||||
|
|
||||||
|
### 1. 导入组件 |
||||||
|
|
||||||
|
```javascript |
||||||
|
import { |
||||||
|
ComprehensiveReport, |
||||||
|
AirConditioningReport, |
||||||
|
LightingReport, |
||||||
|
PumpReport |
||||||
|
} from "./components"; |
||||||
|
|
||||||
|
export default { |
||||||
|
components: { |
||||||
|
ComprehensiveReport, |
||||||
|
AirConditioningReport, |
||||||
|
LightingReport, |
||||||
|
PumpReport |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 2. 动态渲染 |
||||||
|
|
||||||
|
```javascript |
||||||
|
data() { |
||||||
|
return { |
||||||
|
currentReportType: 'comprehensive', // comprehensive | airConditioning | lighting | pump |
||||||
|
reportData: {} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 3. 切换报表类型 |
||||||
|
|
||||||
|
```javascript |
||||||
|
methods: { |
||||||
|
generateReport(reportType) { |
||||||
|
this.currentReportType = reportType; |
||||||
|
// 获取报表数据... |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## 📝 扩展指南 |
||||||
|
|
||||||
|
### 添加新报表类型 |
||||||
|
|
||||||
|
#### 步骤 1: 创建新组件 |
||||||
|
|
||||||
|
在 `components` 目录下创建新的 `.vue` 文件: |
||||||
|
|
||||||
|
```vue |
||||||
|
<template> |
||||||
|
<div class="custom-report report-wrapper"> |
||||||
|
<!-- 报表内容 --> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: "CustomReport", |
||||||
|
props: { |
||||||
|
reportData: Object, |
||||||
|
userName: String |
||||||
|
} |
||||||
|
// ... 其他逻辑 |
||||||
|
} |
||||||
|
</script> |
||||||
|
``` |
||||||
|
|
||||||
|
#### 步骤 2: 导出组件 |
||||||
|
|
||||||
|
在 `index.js` 中添加导出: |
||||||
|
|
||||||
|
```javascript |
||||||
|
export { default as CustomReport } from './CustomReport.vue' |
||||||
|
``` |
||||||
|
|
||||||
|
#### 步骤 3: 注册组件 |
||||||
|
|
||||||
|
在主页面中导入并使用: |
||||||
|
|
||||||
|
```javascript |
||||||
|
import { CustomReport } from "./components"; |
||||||
|
|
||||||
|
export default { |
||||||
|
components: { |
||||||
|
CustomReport |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
reportTypeMap: { |
||||||
|
custom: "CustomReport" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 自定义图表配置 |
||||||
|
|
||||||
|
修改组件中的 `getChartOption` 方法: |
||||||
|
|
||||||
|
```javascript |
||||||
|
methods: { |
||||||
|
getCustomChartOption() { |
||||||
|
return { |
||||||
|
title: { text: '自定义图表' }, |
||||||
|
xAxis: { /* ... */ }, |
||||||
|
yAxis: { /* ... */ }, |
||||||
|
series: [/* ... */] |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## ✅ 优势总结 |
||||||
|
|
||||||
|
### 1. **模块化** |
||||||
|
- 每个报表独立封装,互不干扰 |
||||||
|
- 便于团队协作开发 |
||||||
|
- 易于单元测试 |
||||||
|
|
||||||
|
### 2. **可复用性** |
||||||
|
- 组件可在不同页面复用 |
||||||
|
- 支持动态加载和切换 |
||||||
|
- 统一的接口规范 |
||||||
|
|
||||||
|
### 3. **可维护性** |
||||||
|
- 代码结构清晰,职责单一 |
||||||
|
- 修改某个报表不影响其他报表 |
||||||
|
- 便于版本管理和回滚 |
||||||
|
|
||||||
|
### 4. **性能优化** |
||||||
|
- 按需加载组件 |
||||||
|
- 图表实例自动销毁,避免内存泄漏 |
||||||
|
- 支持懒加载和虚拟滚动 |
||||||
|
|
||||||
|
### 5. **易扩展** |
||||||
|
- 新增报表类型只需添加新组件 |
||||||
|
- 支持插件化开发 |
||||||
|
- 向后兼容性好 |
||||||
|
|
||||||
|
## 🔍 调试技巧 |
||||||
|
|
||||||
|
### 查看组件实例 |
||||||
|
|
||||||
|
```javascript |
||||||
|
// 在控制台查看当前激活的报表组件 |
||||||
|
console.log(this.$children.find( |
||||||
|
child => child.$options.name === this.reportComponent |
||||||
|
)); |
||||||
|
``` |
||||||
|
|
||||||
|
### 检查图表渲染 |
||||||
|
|
||||||
|
```javascript |
||||||
|
// 验证图表是否正确初始化 |
||||||
|
this.chartInstances.forEach((chart, index) => { |
||||||
|
console.log(`Chart ${index}:`, chart.isDisposed()); |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
### 性能监控 |
||||||
|
|
||||||
|
```javascript |
||||||
|
// 记录组件渲染时间 |
||||||
|
const start = performance.now(); |
||||||
|
this.$nextTick(() => { |
||||||
|
const end = performance.now(); |
||||||
|
console.log(`Render time: ${end - start}ms`); |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
## 📖 最佳实践 |
||||||
|
|
||||||
|
1. **Props 验证**: 始终为 props 定义类型和默认值 |
||||||
|
2. **事件命名**: 使用 kebab-case 命名自定义事件 |
||||||
|
3. **样式隔离**: 使用 `scoped` 避免样式污染 |
||||||
|
4. **资源清理**: 及时销毁定时器、图表实例等资源 |
||||||
|
5. **错误处理**: 添加完善的错误捕获和用户提示 |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
**版本**: v2.0.0 |
||||||
|
**更新日期**: 2026-03-06 |
||||||
|
**文档状态**: 已完成 ✅ |
||||||
@ -0,0 +1,700 @@ |
|||||||
|
<template> |
||||||
|
<div class="air-conditioning-ai-saving report-wrapper"> |
||||||
|
<!-- 报表标题 --> |
||||||
|
<div class="report-title-section"> |
||||||
|
<h1>{{ reportData.title }}</h1> |
||||||
|
<p class="report-time">生成时间:{{ reportData.generateTime }}</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 报表摘要 --> |
||||||
|
<div class="report-summary"> |
||||||
|
<h3>AI空调节能策略报告</h3> |
||||||
|
<p>{{ reportData.summary }}</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 策略配置区域 --> |
||||||
|
<div class="strategy-config"> |
||||||
|
<h2>一、节能策略配置</h2> |
||||||
|
|
||||||
|
<!-- 目标选择 --> |
||||||
|
<div class="target-selection"> |
||||||
|
<h3>优化目标</h3> |
||||||
|
<el-radio-group v-model="selectedTarget" size="small"> |
||||||
|
<el-radio label="cost">成本最低</el-radio> |
||||||
|
<el-radio label="comfort">舒适度品质最优</el-radio> |
||||||
|
<el-radio label="balanced">成本与品质均衡</el-radio> |
||||||
|
</el-radio-group> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 系统选择 --> |
||||||
|
<div class="system-selection"> |
||||||
|
<h3>适用系统</h3> |
||||||
|
<el-checkbox-group v-model="selectedSystems"> |
||||||
|
<el-checkbox label="chiller">冷水机组</el-checkbox> |
||||||
|
<el-checkbox label="pump">水泵系统</el-checkbox> |
||||||
|
<el-checkbox label="terminal">空调末端</el-checkbox> |
||||||
|
<el-checkbox label="heatPump">热泵系统</el-checkbox> |
||||||
|
</el-checkbox-group> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 执行模式 --> |
||||||
|
<div class="execution-mode"> |
||||||
|
<h3>执行方式</h3> |
||||||
|
<el-radio-group v-model="executionMode" size="small"> |
||||||
|
<el-radio label="auto">自动执行(动态调整)</el-radio> |
||||||
|
<el-radio label="suggestion">仅建议</el-radio> |
||||||
|
</el-radio-group> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 系统参数优化 --> |
||||||
|
<div class="system-optimization"> |
||||||
|
<h2>二、系统参数优化策略</h2> |
||||||
|
|
||||||
|
<el-tabs v-model="activeSystemTab" type="card"> |
||||||
|
<!-- 冷水机组 --> |
||||||
|
<el-tab-pane label="冷水机组" name="chiller"> |
||||||
|
<div class="system-parameters"> |
||||||
|
<h3>冷水机组参数优化</h3> |
||||||
|
|
||||||
|
<el-form :inline="true" size="small" label-width="120px"> |
||||||
|
<el-form-item label="供水温度设定值"> |
||||||
|
<el-input-number |
||||||
|
v-model="chillerParams.supplyTemp" |
||||||
|
:min="5" |
||||||
|
:max="15" |
||||||
|
controls-position="right" |
||||||
|
/> °C |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="回水温度设定值"> |
||||||
|
<el-input-number |
||||||
|
v-model="chillerParams.returnTemp" |
||||||
|
:min="10" |
||||||
|
:max="20" |
||||||
|
controls-position="right" |
||||||
|
/> °C |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="运行台数"> |
||||||
|
<el-input-number |
||||||
|
v-model="chillerParams.runningUnits" |
||||||
|
:min="1" |
||||||
|
:max="8" |
||||||
|
controls-position="right" |
||||||
|
/> 台 |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="负载率优化"> |
||||||
|
<el-slider |
||||||
|
v-model="chillerParams.loadOptimization" |
||||||
|
:min="70" |
||||||
|
:max="100" |
||||||
|
show-input |
||||||
|
/> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
</div> |
||||||
|
</el-tab-pane> |
||||||
|
|
||||||
|
<!-- 水泵系统 --> |
||||||
|
<el-tab-pane label="水泵系统" name="pump"> |
||||||
|
<div class="system-parameters"> |
||||||
|
<h3>水泵系统参数优化</h3> |
||||||
|
|
||||||
|
<el-form :inline="true" size="small" label-width="120px"> |
||||||
|
<el-form-item label="变频水泵频率"> |
||||||
|
<el-input-number |
||||||
|
v-model="pumpParams.frequency" |
||||||
|
:min="30" |
||||||
|
:max="50" |
||||||
|
controls-position="right" |
||||||
|
/> Hz |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="供水压力设定"> |
||||||
|
<el-input-number |
||||||
|
v-model="pumpParams.supplyPressure" |
||||||
|
:min="0.2" |
||||||
|
:max="0.6" |
||||||
|
:step="0.1" |
||||||
|
controls-position="right" |
||||||
|
/> MPa |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="回水压力设定"> |
||||||
|
<el-input-number |
||||||
|
v-model="pumpParams.returnPressure" |
||||||
|
:min="0.1" |
||||||
|
:max="0.4" |
||||||
|
:step="0.1" |
||||||
|
controls-position="right" |
||||||
|
/> MPa |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="水泵运行模式"> |
||||||
|
<el-select v-model="pumpParams.operationMode" size="small"> |
||||||
|
<el-option label="恒压差" value="constantDiff"/> |
||||||
|
<el-option label="变压差" value="variableDiff"/> |
||||||
|
<el-option label="智能调度" value="smartSchedule"/> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
</div> |
||||||
|
</el-tab-pane> |
||||||
|
|
||||||
|
<!-- 空调末端 --> |
||||||
|
<el-tab-pane label="空调末端" name="terminal"> |
||||||
|
<div class="system-parameters"> |
||||||
|
<h3>空调末端参数优化</h3> |
||||||
|
|
||||||
|
<el-form :inline="true" size="small" label-width="120px"> |
||||||
|
<el-form-item label="风机转速"> |
||||||
|
<el-input-number |
||||||
|
v-model="terminalParams.fanSpeed" |
||||||
|
:min="20" |
||||||
|
:max="100" |
||||||
|
controls-position="right" |
||||||
|
/> % |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="风阀开度"> |
||||||
|
<el-input-number |
||||||
|
v-model="terminalParams.damperOpening" |
||||||
|
:min="10" |
||||||
|
:max="100" |
||||||
|
controls-position="right" |
||||||
|
/> % |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="室内温度设定"> |
||||||
|
<el-input-number |
||||||
|
v-model="terminalParams.roomTemp" |
||||||
|
:min="22" |
||||||
|
:max="26" |
||||||
|
controls-position="right" |
||||||
|
/> °C |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="CO₂浓度阈值"> |
||||||
|
<el-input-number |
||||||
|
v-model="terminalParams.co2Threshold" |
||||||
|
:min="600" |
||||||
|
:max="1000" |
||||||
|
controls-position="right" |
||||||
|
/> ppm |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
</div> |
||||||
|
</el-tab-pane> |
||||||
|
|
||||||
|
<!-- 热泵系统 --> |
||||||
|
<el-tab-pane label="热泵系统" name="heatPump"> |
||||||
|
<div class="system-parameters"> |
||||||
|
<h3>热泵系统参数优化</h3> |
||||||
|
|
||||||
|
<el-form :inline="true" size="small" label-width="120px"> |
||||||
|
<el-form-item label="制热供水温度"> |
||||||
|
<el-input-number |
||||||
|
v-model="heatPumpParams.heatingTemp" |
||||||
|
:min="35" |
||||||
|
:max="55" |
||||||
|
controls-position="right" |
||||||
|
/> °C |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="制冷供水温度"> |
||||||
|
<el-input-number |
||||||
|
v-model="heatPumpParams.coolingTemp" |
||||||
|
:min="5" |
||||||
|
:max="15" |
||||||
|
controls-position="right" |
||||||
|
/> °C |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="运行模式"> |
||||||
|
<el-select v-model="heatPumpParams.operationMode" size="small"> |
||||||
|
<el-option label="制热优先" value="heatingFirst"/> |
||||||
|
<el-option label="制冷优先" value="coolingFirst"/> |
||||||
|
<el-option label="平衡模式" value="balancedMode"/> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="能效优化等级"> |
||||||
|
<el-slider |
||||||
|
v-model="heatPumpParams.efficiencyLevel" |
||||||
|
:min="1" |
||||||
|
:max="5" |
||||||
|
show-input |
||||||
|
/> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
</div> |
||||||
|
</el-tab-pane> |
||||||
|
</el-tabs> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 区域策略配置 --> |
||||||
|
<div class="area-strategy-config"> |
||||||
|
<h2>三、区域策略配置</h2> |
||||||
|
|
||||||
|
<el-table :data="areaStrategies" size="small" border style="margin-bottom: 15px;"> |
||||||
|
<el-table-column prop="areaName" label="区域名称" width="120"/> |
||||||
|
<el-table-column prop="temperatureRange" label="温度范围(°C)" width="120"> |
||||||
|
<template slot-scope="{row}"> |
||||||
|
<el-input-number |
||||||
|
v-model="row.minTemp" |
||||||
|
:min="18" |
||||||
|
:max="28" |
||||||
|
size="small" |
||||||
|
style="width: 80px;" |
||||||
|
/> - |
||||||
|
<el-input-number |
||||||
|
v-model="row.maxTemp" |
||||||
|
:min="18" |
||||||
|
:max="28" |
||||||
|
size="small" |
||||||
|
style="width: 80px;" |
||||||
|
/> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column prop="humidityRange" label="湿度范围(%)" width="120"> |
||||||
|
<template slot-scope="{row}"> |
||||||
|
<el-input-number |
||||||
|
v-model="row.minHumidity" |
||||||
|
:min="30" |
||||||
|
:max="70" |
||||||
|
size="small" |
||||||
|
style="width: 80px;" |
||||||
|
/> - |
||||||
|
<el-input-number |
||||||
|
v-model="row.maxHumidity" |
||||||
|
:min="30" |
||||||
|
:max="70" |
||||||
|
size="small" |
||||||
|
style="width: 80px;" |
||||||
|
/> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column prop="co2Threshold" label="CO₂阈值(ppm)" width="100"> |
||||||
|
<template slot-scope="{row}"> |
||||||
|
<el-input-number |
||||||
|
v-model="row.co2Threshold" |
||||||
|
:min="500" |
||||||
|
:max="1200" |
||||||
|
size="small" |
||||||
|
style="width: 100px;" |
||||||
|
/> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column prop="priority" label="优先级" width="100"> |
||||||
|
<template slot-scope="{row}"> |
||||||
|
<el-select v-model="row.priority" size="small"> |
||||||
|
<el-option label="高" value="high"/> |
||||||
|
<el-option label="中" value="medium"/> |
||||||
|
<el-option label="低" value="low"/> |
||||||
|
</el-select> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="操作" width="80"> |
||||||
|
<template slot-scope="{row, $index}"> |
||||||
|
<el-button |
||||||
|
type="text" |
||||||
|
size="small" |
||||||
|
@click="removeAreaStrategy($index)" |
||||||
|
>删除</el-button> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
|
||||||
|
<el-button |
||||||
|
type="primary" |
||||||
|
size="small" |
||||||
|
icon="el-icon-plus" |
||||||
|
@click="addAreaStrategy" |
||||||
|
>添加区域策略</el-button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 效果预览与评估 --> |
||||||
|
<div class="effect-preview"> |
||||||
|
<h2>四、策略效果预览</h2> |
||||||
|
|
||||||
|
<div class="preview-metrics"> |
||||||
|
<div class="metric-card"> |
||||||
|
<div class="metric-value">{{ estimatedEnergySaving }}%</div> |
||||||
|
<div class="metric-label">预计节能率</div> |
||||||
|
</div> |
||||||
|
<div class="metric-card"> |
||||||
|
<div class="metric-value">{{ comfortIndex }}</div> |
||||||
|
<div class="metric-label">舒适度指数</div> |
||||||
|
</div> |
||||||
|
<div class="metric-card"> |
||||||
|
<div class="metric-value">{{ costReduction }}%</div> |
||||||
|
<div class="metric-label">成本降低</div> |
||||||
|
</div> |
||||||
|
<div class="metric-card"> |
||||||
|
<div class="metric-value">{{ co2Reduction }}吨</div> |
||||||
|
<div class="metric-label">CO₂减排</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 节能效果图表 --> |
||||||
|
<div class="charts-container"> |
||||||
|
<div ref="energyTrendChart" class="chart-box"></div> |
||||||
|
<div ref="comfortAnalysisChart" class="chart-box"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 操作按钮 --> |
||||||
|
<div class="action-buttons"> |
||||||
|
<el-button type="primary" @click="saveStrategy">保存策略</el-button> |
||||||
|
<el-button type="success" @click="executeStrategy">立即执行</el-button> |
||||||
|
<el-button @click="resetStrategy">重置</el-button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import * as echarts from 'echarts'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'AirConditioningAISaving', |
||||||
|
props: { |
||||||
|
reportData: { |
||||||
|
type: Object, |
||||||
|
required: true |
||||||
|
}, |
||||||
|
userName: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
} |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
selectedTarget: 'balanced', |
||||||
|
selectedSystems: ['chiller', 'pump', 'terminal', 'heatPump'], |
||||||
|
executionMode: 'auto', |
||||||
|
activeSystemTab: 'chiller', |
||||||
|
|
||||||
|
// 系统参数配置 |
||||||
|
chillerParams: { |
||||||
|
supplyTemp: 7, |
||||||
|
returnTemp: 12, |
||||||
|
runningUnits: 3, |
||||||
|
loadOptimization: 85 |
||||||
|
}, |
||||||
|
pumpParams: { |
||||||
|
frequency: 40, |
||||||
|
supplyPressure: 0.4, |
||||||
|
returnPressure: 0.2, |
||||||
|
operationMode: 'smartSchedule' |
||||||
|
}, |
||||||
|
terminalParams: { |
||||||
|
fanSpeed: 70, |
||||||
|
damperOpening: 80, |
||||||
|
roomTemp: 24, |
||||||
|
co2Threshold: 800 |
||||||
|
}, |
||||||
|
heatPumpParams: { |
||||||
|
heatingTemp: 45, |
||||||
|
coolingTemp: 7, |
||||||
|
operationMode: 'balancedMode', |
||||||
|
efficiencyLevel: 4 |
||||||
|
}, |
||||||
|
|
||||||
|
// 区域策略配置 |
||||||
|
areaStrategies: [ |
||||||
|
{ areaName: '办公区', minTemp: 22, maxTemp: 26, minHumidity: 40, maxHumidity: 60, co2Threshold: 800, priority: 'high' }, |
||||||
|
{ areaName: '会议室', minTemp: 22, maxTemp: 25, minHumidity: 45, maxHumidity: 65, co2Threshold: 900, priority: 'high' }, |
||||||
|
{ areaName: '走廊', minTemp: 23, maxTemp: 27, minHumidity: 35, maxHumidity: 65, co2Threshold: 1000, priority: 'medium' }, |
||||||
|
{ areaName: '设备间', minTemp: 20, maxTemp: 28, minHumidity: 30, maxHumidity: 70, co2Threshold: 1200, priority: 'low' } |
||||||
|
], |
||||||
|
|
||||||
|
// 效果预览数据 |
||||||
|
estimatedEnergySaving: 32, |
||||||
|
comfortIndex: 8.5, |
||||||
|
costReduction: 28, |
||||||
|
co2Reduction: 15.6 |
||||||
|
}; |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
this.initCharts(); |
||||||
|
}, |
||||||
|
beforeDestroy() { |
||||||
|
this.disposeCharts(); |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
initCharts() { |
||||||
|
// 能耗趋势图 |
||||||
|
if (this.$refs.energyTrendChart) { |
||||||
|
const energyTrendChart = echarts.init(this.$refs.energyTrendChart); |
||||||
|
energyTrendChart.setOption({ |
||||||
|
title: { text: '月度能耗与节能效果', left: 'center' }, |
||||||
|
tooltip: { trigger: 'axis' }, |
||||||
|
legend: { top: 'bottom' }, |
||||||
|
xAxis: { type: 'category', data: ['1月', '2月', '3月', '4月', '5月', '6月'] }, |
||||||
|
yAxis: [{ type: 'value', name: '能耗(kWh)' }, { type: 'value', name: '节能率(%)', position: 'right' }], |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: '原始能耗', |
||||||
|
type: 'bar', |
||||||
|
data: [12000, 11500, 11000, 10500, 10000, 9500], |
||||||
|
itemStyle: { color: '#6b8a9e' } |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: '优化后能耗', |
||||||
|
type: 'bar', |
||||||
|
data: [9500, 9000, 8500, 8000, 7500, 7000], |
||||||
|
itemStyle: { color: '#0ac1c7' } |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: '节能率', |
||||||
|
type: 'line', |
||||||
|
yAxisIndex: 1, |
||||||
|
data: [20, 22, 23, 24, 25, 26], |
||||||
|
smooth: true, |
||||||
|
lineStyle: { width: 3 }, |
||||||
|
itemStyle: { color: '#ff9900' } |
||||||
|
} |
||||||
|
] |
||||||
|
}); |
||||||
|
this.energyTrendChart = energyTrendChart; |
||||||
|
} |
||||||
|
|
||||||
|
// 舒适度分析图 |
||||||
|
if (this.$refs.comfortAnalysisChart) { |
||||||
|
const comfortAnalysisChart = echarts.init(this.$refs.comfortAnalysisChart); |
||||||
|
comfortAnalysisChart.setOption({ |
||||||
|
title: { text: '舒适度与能效平衡分析', left: 'center' }, |
||||||
|
tooltip: { trigger: 'item' }, |
||||||
|
radar: { |
||||||
|
indicator: [ |
||||||
|
{ name: '温度舒适度', max: 10 }, |
||||||
|
{ name: '湿度舒适度', max: 10 }, |
||||||
|
{ name: '空气质量', max: 10 }, |
||||||
|
{ name: '噪音水平', max: 10 }, |
||||||
|
{ name: '能效表现', max: 10 } |
||||||
|
] |
||||||
|
}, |
||||||
|
series: [{ |
||||||
|
name: '舒适度分析', |
||||||
|
type: 'radar', |
||||||
|
data: [ |
||||||
|
{ |
||||||
|
value: [8.5, 8.2, 8.8, 9.0, 7.5], |
||||||
|
name: '当前策略' |
||||||
|
} |
||||||
|
], |
||||||
|
itemStyle: { color: '#0ac1c7' }, |
||||||
|
lineStyle: { width: 3 } |
||||||
|
}] |
||||||
|
}); |
||||||
|
this.comfortAnalysisChart = comfortAnalysisChart; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
disposeCharts() { |
||||||
|
if (this.energyTrendChart) { |
||||||
|
this.energyTrendChart.dispose(); |
||||||
|
this.energyTrendChart = null; |
||||||
|
} |
||||||
|
if (this.comfortAnalysisChart) { |
||||||
|
this.comfortAnalysisChart.dispose(); |
||||||
|
this.comfortAnalysisChart = null; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
addAreaStrategy() { |
||||||
|
this.areaStrategies.push({ |
||||||
|
areaName: '新区域', |
||||||
|
minTemp: 22, |
||||||
|
maxTemp: 26, |
||||||
|
minHumidity: 40, |
||||||
|
maxHumidity: 60, |
||||||
|
co2Threshold: 800, |
||||||
|
priority: 'medium' |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
removeAreaStrategy(index) { |
||||||
|
if (this.areaStrategies.length > 1) { |
||||||
|
this.areaStrategies.splice(index, 1); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
saveStrategy() { |
||||||
|
this.$message.success('策略保存成功!'); |
||||||
|
// 这里可以调用API保存策略 |
||||||
|
}, |
||||||
|
|
||||||
|
executeStrategy() { |
||||||
|
this.$confirm('确定要立即执行此AI空调节能策略吗?', '确认执行', { |
||||||
|
confirmButtonText: '确定执行', |
||||||
|
cancelButtonText: '取消', |
||||||
|
type: 'warning' |
||||||
|
}).then(() => { |
||||||
|
this.$message.success('策略已开始执行!'); |
||||||
|
// 这里可以调用API执行策略 |
||||||
|
}).catch(() => { |
||||||
|
// 取消操作 |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
resetStrategy() { |
||||||
|
this.$confirm('确定要重置所有策略配置吗?', '确认重置', { |
||||||
|
confirmButtonText: '确定重置', |
||||||
|
cancelButtonText: '取消', |
||||||
|
type: 'info' |
||||||
|
}).then(() => { |
||||||
|
// 重置到默认配置 |
||||||
|
this.chillerParams = { supplyTemp: 7, returnTemp: 12, runningUnits: 3, loadOptimization: 85 }; |
||||||
|
this.pumpParams = { frequency: 40, supplyPressure: 0.4, returnPressure: 0.2, operationMode: 'smartSchedule' }; |
||||||
|
this.terminalParams = { fanSpeed: 70, damperOpening: 80, roomTemp: 24, co2Threshold: 800 }; |
||||||
|
this.heatPumpParams = { heatingTemp: 45, coolingTemp: 7, operationMode: 'balancedMode', efficiencyLevel: 4 }; |
||||||
|
this.areaStrategies = [ |
||||||
|
{ areaName: '办公区', minTemp: 22, maxTemp: 26, minHumidity: 40, maxHumidity: 60, co2Threshold: 800, priority: 'high' }, |
||||||
|
{ areaName: '会议室', minTemp: 22, maxTemp: 25, minHumidity: 45, maxHumidity: 65, co2Threshold: 900, priority: 'high' }, |
||||||
|
{ areaName: '走廊', minTemp: 23, maxTemp: 27, minHumidity: 35, maxHumidity: 65, co2Threshold: 1000, priority: 'medium' }, |
||||||
|
{ areaName: '设备间', minTemp: 20, maxTemp: 28, minHumidity: 30, maxHumidity: 70, co2Threshold: 1200, priority: 'low' } |
||||||
|
]; |
||||||
|
this.$message.success('策略已重置!'); |
||||||
|
}).catch(() => { |
||||||
|
// 取消操作 |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.air-conditioning-ai-saving { |
||||||
|
.strategy-config { |
||||||
|
margin-bottom: 20px; |
||||||
|
|
||||||
|
h3 { |
||||||
|
margin: 15px 0 10px 0; |
||||||
|
color: #0ac1c7; |
||||||
|
} |
||||||
|
|
||||||
|
.el-radio-group, |
||||||
|
.el-checkbox-group { |
||||||
|
display: flex; |
||||||
|
flex-wrap: wrap; |
||||||
|
gap: 15px; |
||||||
|
} |
||||||
|
|
||||||
|
.el-radio, |
||||||
|
.el-checkbox { |
||||||
|
margin-right: 20px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.system-optimization { |
||||||
|
margin-bottom: 20px; |
||||||
|
|
||||||
|
.system-parameters { |
||||||
|
padding: 15px; |
||||||
|
background: rgba(255, 255, 255, 0.05); |
||||||
|
border-radius: 8px; |
||||||
|
|
||||||
|
h3 { |
||||||
|
margin-bottom: 15px; |
||||||
|
color: #0ac1c7; |
||||||
|
} |
||||||
|
|
||||||
|
::v-deep .el-form { |
||||||
|
.el-form-item { |
||||||
|
margin-right: 20px; |
||||||
|
margin-bottom: 15px; |
||||||
|
} |
||||||
|
|
||||||
|
.el-input-number { |
||||||
|
width: 120px; |
||||||
|
} |
||||||
|
|
||||||
|
.el-slider { |
||||||
|
width: 200px; |
||||||
|
|
||||||
|
.el-slider__runway { |
||||||
|
height: 6px; |
||||||
|
background: #2d3f52; |
||||||
|
} |
||||||
|
|
||||||
|
.el-slider__bar { |
||||||
|
height: 6px; |
||||||
|
background: #0ac1c7; |
||||||
|
} |
||||||
|
|
||||||
|
.el-slider__button { |
||||||
|
width: 16px; |
||||||
|
height: 16px; |
||||||
|
border: 2px solid #0ac1c7; |
||||||
|
background: #1a2a3a; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.area-strategy-config { |
||||||
|
margin-bottom: 20px; |
||||||
|
|
||||||
|
h2 { |
||||||
|
margin-bottom: 15px; |
||||||
|
} |
||||||
|
|
||||||
|
::v-deep .el-table { |
||||||
|
.el-table__header th { |
||||||
|
background: #2d3f52; |
||||||
|
color: #e0e6ed; |
||||||
|
} |
||||||
|
|
||||||
|
.el-table__body td { |
||||||
|
background: #1a2a3a; |
||||||
|
color: #e0e6ed; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.effect-preview { |
||||||
|
margin-bottom: 20px; |
||||||
|
|
||||||
|
.preview-metrics { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
||||||
|
gap: 15px; |
||||||
|
margin-bottom: 20px; |
||||||
|
|
||||||
|
.metric-card { |
||||||
|
background: rgba(10, 193, 199, 0.1); |
||||||
|
border: 1px solid rgba(10, 193, 199, 0.3); |
||||||
|
border-radius: 8px; |
||||||
|
padding: 15px; |
||||||
|
text-align: center; |
||||||
|
|
||||||
|
.metric-value { |
||||||
|
font-size: 24px; |
||||||
|
font-weight: bold; |
||||||
|
color: #0ac1c7; |
||||||
|
margin-bottom: 5px; |
||||||
|
} |
||||||
|
|
||||||
|
.metric-label { |
||||||
|
font-size: 14px; |
||||||
|
color: #a0b3c6; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.charts-container { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 1fr 1fr; |
||||||
|
gap: 20px; |
||||||
|
|
||||||
|
.chart-box { |
||||||
|
height: 300px; |
||||||
|
background: #1a2a3a; |
||||||
|
border-radius: 8px; |
||||||
|
padding: 15px; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.action-buttons { |
||||||
|
text-align: center; |
||||||
|
padding: 20px 0; |
||||||
|
|
||||||
|
.el-button { |
||||||
|
margin: 0 10px; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,536 @@ |
|||||||
|
<template> |
||||||
|
<div class="air-conditioning-report report-wrapper"> |
||||||
|
<!-- 报表标题 --> |
||||||
|
<div class="report-title-section"> |
||||||
|
<h1>{{ reportData.title }}</h1> |
||||||
|
<p class="report-time">生成时间:{{ reportData.generateTime }}</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 报表摘要 --> |
||||||
|
<div class="report-summary"> |
||||||
|
<h3>报告摘要</h3> |
||||||
|
<p>{{ reportData.summary }}</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 报表主体内容 --> |
||||||
|
<div class="report-main"> |
||||||
|
<!-- 项目情况 --> |
||||||
|
<section class="report-section"> |
||||||
|
<h2>一、项目情况</h2> |
||||||
|
<div class="section-content"> |
||||||
|
<p>{{ reportData.projectInfo.description }}</p> |
||||||
|
<el-table |
||||||
|
v-if="reportData.projectInfo.configTable" |
||||||
|
:data="reportData.projectInfo.configTable" |
||||||
|
border |
||||||
|
size="small" |
||||||
|
> |
||||||
|
<el-table-column |
||||||
|
v-for="col in reportData.projectInfo.configTableColumns" |
||||||
|
:key="col.prop" |
||||||
|
:prop="col.prop" |
||||||
|
:label="col.label" |
||||||
|
/> |
||||||
|
</el-table> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<!-- 设备概述 --> |
||||||
|
<section class="report-section"> |
||||||
|
<h2>二、设备概述</h2> |
||||||
|
<div class="section-content"> |
||||||
|
<p>{{ reportData.equipmentOverview.description }}</p> |
||||||
|
<div class="charts-container"> |
||||||
|
<!-- 设备用电占比饼图 --> |
||||||
|
<div ref="devicePieChart" class="chart-box"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<!-- 运行数据 --> |
||||||
|
<section class="report-section"> |
||||||
|
<h2>三、运行数据</h2> |
||||||
|
<div class="section-content"> |
||||||
|
<div class="charts-container"> |
||||||
|
<!-- 能效趋势图 --> |
||||||
|
<div ref="efficiencyTrendChart" class="chart-box"></div> |
||||||
|
<!-- 每日用电趋势图 --> |
||||||
|
<div ref="dailyEnergyChart" class="chart-box"></div> |
||||||
|
</div> |
||||||
|
<el-table |
||||||
|
v-if="reportData.operationData.dataTable" |
||||||
|
:data="reportData.operationData.dataTable" |
||||||
|
border |
||||||
|
stripe |
||||||
|
size="small" |
||||||
|
> |
||||||
|
<el-table-column |
||||||
|
v-for="col in reportData.operationData.tableColumns" |
||||||
|
:key="col.prop" |
||||||
|
:prop="col.prop" |
||||||
|
:label="col.label" |
||||||
|
/> |
||||||
|
</el-table> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<!-- 分析总结 --> |
||||||
|
<section class="report-section"> |
||||||
|
<h2>四、分析总结</h2> |
||||||
|
<div class="section-content"> |
||||||
|
<div class="analysis-points"> |
||||||
|
<div |
||||||
|
v-for="(point, index) in reportData.analysisSummary.points" |
||||||
|
:key="index" |
||||||
|
class="analysis-point" |
||||||
|
> |
||||||
|
<i class="el-icon-caret-right"></i> |
||||||
|
<span>{{ point }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div v-if="reportData.analysisSummary.suggestions" class="suggestions"> |
||||||
|
<h4>优化建议:</h4> |
||||||
|
<ul> |
||||||
|
<li |
||||||
|
v-for="(suggestion, index) in reportData.analysisSummary.suggestions" |
||||||
|
:key="index" |
||||||
|
> |
||||||
|
{{ suggestion }} |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 报表底部信息 --> |
||||||
|
<div class="report-footer"> |
||||||
|
<div class="footer-info"> |
||||||
|
<span>操作员:{{ userName }}</span> |
||||||
|
<span>生成日期:{{ operationDate }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import * as echarts from "echarts"; |
||||||
|
import { format, getDay } from "@/utils/datetime"; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "AirConditioningReport", |
||||||
|
props: { |
||||||
|
reportData: { |
||||||
|
type: Object, |
||||||
|
default: () => ({}) |
||||||
|
}, |
||||||
|
userName: { |
||||||
|
type: String, |
||||||
|
default: "" |
||||||
|
} |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
operationDate: getDay(0), |
||||||
|
chartInstances: [] |
||||||
|
}; |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
this.$nextTick(() => { |
||||||
|
this.renderCharts(); |
||||||
|
}); |
||||||
|
}, |
||||||
|
beforeDestroy() { |
||||||
|
this.chartInstances.forEach((chart) => { |
||||||
|
if (chart) chart.dispose(); |
||||||
|
}); |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
renderCharts() { |
||||||
|
// 销毁旧图表 |
||||||
|
this.chartInstances.forEach((chart) => { |
||||||
|
if (chart) chart.dispose(); |
||||||
|
}); |
||||||
|
this.chartInstances = []; |
||||||
|
|
||||||
|
// 渲染设备用电占比饼图 |
||||||
|
if (this.$refs.devicePieChart) { |
||||||
|
const chart = echarts.init(this.$refs.devicePieChart); |
||||||
|
chart.setOption(this.getDevicePieOption()); |
||||||
|
this.chartInstances.push(chart); |
||||||
|
} |
||||||
|
|
||||||
|
// 渲染能效趋势图 |
||||||
|
if (this.$refs.efficiencyTrendChart) { |
||||||
|
const chart = echarts.init(this.$refs.efficiencyTrendChart); |
||||||
|
chart.setOption(this.getEfficiencyTrendOption()); |
||||||
|
this.chartInstances.push(chart); |
||||||
|
} |
||||||
|
|
||||||
|
// 渲染每日用电趋势图 |
||||||
|
if (this.$refs.dailyEnergyChart) { |
||||||
|
const chart = echarts.init(this.$refs.dailyEnergyChart); |
||||||
|
chart.setOption(this.getDailyEnergyOption()); |
||||||
|
this.chartInstances.push(chart); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// 设备用电占比饼图配置 |
||||||
|
getDevicePieOption() { |
||||||
|
const width = this.$refs.devicePieChart?.clientWidth || 400; |
||||||
|
const titleFontSize = width / 50; |
||||||
|
|
||||||
|
return { |
||||||
|
title: { |
||||||
|
text: "制冷设备组用电占比", |
||||||
|
left: "center", |
||||||
|
textStyle: { |
||||||
|
fontSize: titleFontSize, |
||||||
|
color: "#333" |
||||||
|
} |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: "item", |
||||||
|
formatter: "{a} <br/>{b}: {c} kWh ({d}%)" |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
orient: "vertical", |
||||||
|
left: "left", |
||||||
|
top: "middle" |
||||||
|
}, |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: "用电占比", |
||||||
|
type: "pie", |
||||||
|
radius: ["40%", "70%"], |
||||||
|
center: ["50%", "50%"], |
||||||
|
data: [ |
||||||
|
{ value: 1850, name: "冷水机组" }, |
||||||
|
{ value: 920, name: "冷冻泵" }, |
||||||
|
{ value: 780, name: "冷却泵" }, |
||||||
|
{ value: 450, name: "冷却塔风机" } |
||||||
|
], |
||||||
|
emphasis: { |
||||||
|
itemStyle: { |
||||||
|
shadowBlur: 10, |
||||||
|
shadowOffsetX: 0, |
||||||
|
shadowColor: "rgba(0, 0, 0, 0.5)" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
}, |
||||||
|
|
||||||
|
// 能效趋势图配置 |
||||||
|
getEfficiencyTrendOption() { |
||||||
|
const width = this.$refs.efficiencyTrendChart?.clientWidth || 400; |
||||||
|
const titleFontSize = width / 50; |
||||||
|
|
||||||
|
return { |
||||||
|
title: { |
||||||
|
text: "制冷系统能效趋势", |
||||||
|
left: "center", |
||||||
|
textStyle: { |
||||||
|
fontSize: titleFontSize, |
||||||
|
color: "#333" |
||||||
|
} |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: "axis" |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
data: ["机组 COP", "系统 COP", "目标 COP"], |
||||||
|
top: "bottom" |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
left: "3%", |
||||||
|
right: "4%", |
||||||
|
bottom: "15%", |
||||||
|
containLabel: true |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: "category", |
||||||
|
boundaryGap: false, |
||||||
|
data: ["00:00", "04:00", "08:00", "12:00", "16:00", "20:00", "23:59"] |
||||||
|
}, |
||||||
|
yAxis: { |
||||||
|
type: "value", |
||||||
|
name: "COP 值" |
||||||
|
}, |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: "机组 COP", |
||||||
|
type: "line", |
||||||
|
smooth: true, |
||||||
|
data: [5.8, 6.2, 6.5, 6.8, 6.6, 6.3, 6.0], |
||||||
|
lineStyle: { |
||||||
|
width: 3, |
||||||
|
color: "#409EFF" |
||||||
|
}, |
||||||
|
areaStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: "rgba(64, 158, 255, 0.5)" }, |
||||||
|
{ offset: 1, color: "rgba(64, 158, 255, 0.05)" } |
||||||
|
]) |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "系统 COP", |
||||||
|
type: "line", |
||||||
|
smooth: true, |
||||||
|
data: [5.2, 5.6, 5.9, 6.1, 5.9, 5.7, 5.4], |
||||||
|
lineStyle: { |
||||||
|
width: 3, |
||||||
|
color: "#67C23A" |
||||||
|
}, |
||||||
|
areaStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: "rgba(103, 194, 58, 0.5)" }, |
||||||
|
{ offset: 1, color: "rgba(103, 194, 58, 0.05)" } |
||||||
|
]) |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "目标 COP", |
||||||
|
type: "line", |
||||||
|
smooth: true, |
||||||
|
data: [6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0], |
||||||
|
lineStyle: { |
||||||
|
width: 2, |
||||||
|
color: "#E6A23C", |
||||||
|
type: "dashed" |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
}, |
||||||
|
|
||||||
|
// 每日用电趋势图配置 |
||||||
|
getDailyEnergyOption() { |
||||||
|
const width = this.$refs.dailyEnergyChart?.clientWidth || 400; |
||||||
|
const titleFontSize = width / 50; |
||||||
|
|
||||||
|
return { |
||||||
|
title: { |
||||||
|
text: "每日用电量趋势", |
||||||
|
left: "center", |
||||||
|
textStyle: { |
||||||
|
fontSize: titleFontSize, |
||||||
|
color: "#333" |
||||||
|
} |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: "axis", |
||||||
|
axisPointer: { |
||||||
|
type: "shadow" |
||||||
|
} |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
data: ["总用电量", "制冷量"], |
||||||
|
top: "bottom" |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
left: "3%", |
||||||
|
right: "4%", |
||||||
|
bottom: "15%", |
||||||
|
containLabel: true |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: "category", |
||||||
|
data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] |
||||||
|
}, |
||||||
|
yAxis: [ |
||||||
|
{ |
||||||
|
type: "value", |
||||||
|
name: "用电量 (kWh)", |
||||||
|
position: "left" |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "value", |
||||||
|
name: "制冷量 (GJ)", |
||||||
|
position: "right" |
||||||
|
} |
||||||
|
], |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: "总用电量", |
||||||
|
type: "bar", |
||||||
|
barWidth: "40%", |
||||||
|
data: [3200, 3450, 3100, 3600, 3350, 2800, 2650], |
||||||
|
itemStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: "#83bff6" }, |
||||||
|
{ offset: 1, color: "#188df0" } |
||||||
|
]) |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "制冷量", |
||||||
|
type: "line", |
||||||
|
yAxisIndex: 1, |
||||||
|
smooth: true, |
||||||
|
data: [18.5, 19.8, 17.6, 20.5, 19.2, 15.8, 14.9], |
||||||
|
lineStyle: { |
||||||
|
width: 3, |
||||||
|
color: "#F56C6C" |
||||||
|
}, |
||||||
|
itemStyle: { |
||||||
|
color: "#F56C6C" |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
@import "~@/assets/styles/variables.scss"; |
||||||
|
|
||||||
|
.report-wrapper { |
||||||
|
background: #fff; |
||||||
|
padding: 0.2rem; |
||||||
|
border-radius: 4px; |
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); |
||||||
|
|
||||||
|
.report-title-section { |
||||||
|
text-align: center; |
||||||
|
border-bottom: 2px solid $blue; |
||||||
|
padding-bottom: 0.12rem; |
||||||
|
margin-bottom: 0.2rem; |
||||||
|
|
||||||
|
h1 { |
||||||
|
margin: 0 0 0.1rem 0; |
||||||
|
font-size: 0.2rem; |
||||||
|
color: $blue; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
.report-time { |
||||||
|
font-size: 0.12rem; |
||||||
|
color: #909399; |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.report-summary { |
||||||
|
background: #ecf5ff; |
||||||
|
padding: 0.12rem; |
||||||
|
border-radius: 3px; |
||||||
|
margin-bottom: 0.2rem; |
||||||
|
border-left: 4px solid $light-blue; |
||||||
|
|
||||||
|
h3 { |
||||||
|
margin: 0 0 0.08rem 0; |
||||||
|
font-size: 0.15rem; |
||||||
|
color: $light-blue; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
p { |
||||||
|
margin: 0; |
||||||
|
font-size: 0.13rem; |
||||||
|
line-height: 1.6; |
||||||
|
color: #606266; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.report-main { |
||||||
|
.report-section { |
||||||
|
margin-bottom: 0.25rem; |
||||||
|
|
||||||
|
h2 { |
||||||
|
font-size: 0.16rem; |
||||||
|
color: $light-blue; |
||||||
|
border-left: 4px solid $light-blue; |
||||||
|
padding-left: 0.1rem; |
||||||
|
margin-bottom: 0.12rem; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
.section-content { |
||||||
|
p { |
||||||
|
font-size: 0.13rem; |
||||||
|
line-height: 1.7; |
||||||
|
color: #606266; |
||||||
|
margin-bottom: 0.12rem; |
||||||
|
} |
||||||
|
|
||||||
|
.charts-container { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); |
||||||
|
gap: 0.15rem; |
||||||
|
margin-bottom: 0.12rem; |
||||||
|
|
||||||
|
.chart-box { |
||||||
|
width: 100%; |
||||||
|
height: 280px; |
||||||
|
background: #fafafa; |
||||||
|
border-radius: 3px; |
||||||
|
padding: 0.1rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.analysis-points { |
||||||
|
.analysis-point { |
||||||
|
display: flex; |
||||||
|
align-items: flex-start; |
||||||
|
gap: 0.06rem; |
||||||
|
margin-bottom: 0.08rem; |
||||||
|
font-size: 0.13rem; |
||||||
|
color: #606266; |
||||||
|
|
||||||
|
i { |
||||||
|
color: $green; |
||||||
|
font-size: 0.14rem; |
||||||
|
margin-top: 0.02rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.suggestions { |
||||||
|
margin-top: 0.12rem; |
||||||
|
padding: 0.12rem; |
||||||
|
background: #fef0f0; |
||||||
|
border-radius: 3px; |
||||||
|
border-left: 4px solid $red; |
||||||
|
|
||||||
|
h4 { |
||||||
|
margin: 0 0 0.08rem 0; |
||||||
|
font-size: 0.14rem; |
||||||
|
color: $red; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
ul { |
||||||
|
margin: 0; |
||||||
|
padding-left: 0.18rem; |
||||||
|
|
||||||
|
li { |
||||||
|
font-size: 0.13rem; |
||||||
|
line-height: 1.7; |
||||||
|
color: #606266; |
||||||
|
margin-bottom: 0.05rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.report-footer { |
||||||
|
border-top: 1px solid #e6e6e6; |
||||||
|
padding-top: 0.12rem; |
||||||
|
margin-top: 0.15rem; |
||||||
|
|
||||||
|
.footer-info { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
font-size: 0.11rem; |
||||||
|
color: #909399; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,527 @@ |
|||||||
|
<template> |
||||||
|
<div class="comprehensive-report report-wrapper"> |
||||||
|
<!-- 报表标题 --> |
||||||
|
<div class="report-title-section"> |
||||||
|
<h1>{{ reportData.title }}</h1> |
||||||
|
<p class="report-time">生成时间:{{ reportData.generateTime }}</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 报表摘要 --> |
||||||
|
<div class="report-summary"> |
||||||
|
<h3>报告摘要</h3> |
||||||
|
<p>{{ reportData.summary }}</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 报表主体内容 --> |
||||||
|
<div class="report-main"> |
||||||
|
<!-- 项目情况 --> |
||||||
|
<section class="report-section"> |
||||||
|
<h2>一、项目情况</h2> |
||||||
|
<div class="section-content"> |
||||||
|
<p>{{ reportData.projectInfo.description }}</p> |
||||||
|
<el-table |
||||||
|
v-if="reportData.projectInfo.configTable" |
||||||
|
:data="reportData.projectInfo.configTable" |
||||||
|
border |
||||||
|
size="small" |
||||||
|
> |
||||||
|
<el-table-column |
||||||
|
v-for="col in reportData.projectInfo.configTableColumns" |
||||||
|
:key="col.prop" |
||||||
|
:prop="col.prop" |
||||||
|
:label="col.label" |
||||||
|
/> |
||||||
|
</el-table> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<!-- 设备概述 --> |
||||||
|
<section class="report-section"> |
||||||
|
<h2>二、设备概述</h2> |
||||||
|
<div class="section-content"> |
||||||
|
<p>{{ reportData.equipmentOverview.description }}</p> |
||||||
|
<div class="charts-container"> |
||||||
|
<!-- 能耗占比饼图 --> |
||||||
|
<div ref="energyPieChart" class="chart-box"></div> |
||||||
|
<!-- 设备用电对比柱状图 --> |
||||||
|
<div ref="deviceBarChart" class="chart-box"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<!-- 运行数据 --> |
||||||
|
<section class="report-section"> |
||||||
|
<h2>三、运行数据</h2> |
||||||
|
<div class="section-content"> |
||||||
|
<div class="charts-container"> |
||||||
|
<!-- 能效趋势折线图 --> |
||||||
|
<div ref="efficiencyLineChart" class="chart-box"></div> |
||||||
|
</div> |
||||||
|
<el-table |
||||||
|
v-if="reportData.operationData.dataTable" |
||||||
|
:data="reportData.operationData.dataTable" |
||||||
|
border |
||||||
|
stripe |
||||||
|
size="small" |
||||||
|
> |
||||||
|
<el-table-column |
||||||
|
v-for="col in reportData.operationData.tableColumns" |
||||||
|
:key="col.prop" |
||||||
|
:prop="col.prop" |
||||||
|
:label="col.label" |
||||||
|
/> |
||||||
|
</el-table> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<!-- 分析总结 --> |
||||||
|
<section class="report-section"> |
||||||
|
<h2>四、分析总结</h2> |
||||||
|
<div class="section-content"> |
||||||
|
<div class="analysis-points"> |
||||||
|
<div |
||||||
|
v-for="(point, index) in reportData.analysisSummary.points" |
||||||
|
:key="index" |
||||||
|
class="analysis-point" |
||||||
|
> |
||||||
|
<i class="el-icon-caret-right"></i> |
||||||
|
<span>{{ point }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div v-if="reportData.analysisSummary.suggestions" class="suggestions"> |
||||||
|
<h4>优化建议:</h4> |
||||||
|
<ul> |
||||||
|
<li |
||||||
|
v-for="(suggestion, index) in reportData.analysisSummary.suggestions" |
||||||
|
:key="index" |
||||||
|
> |
||||||
|
{{ suggestion }} |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 报表底部信息 --> |
||||||
|
<div class="report-footer"> |
||||||
|
<div class="footer-info"> |
||||||
|
<span>操作员:{{ userName }}</span> |
||||||
|
<span>生成日期:{{ operationDate }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import * as echarts from "echarts"; |
||||||
|
import { format, getDay } from "@/utils/datetime"; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "ComprehensiveReport", |
||||||
|
props: { |
||||||
|
reportData: { |
||||||
|
type: Object, |
||||||
|
default: () => ({}) |
||||||
|
}, |
||||||
|
userName: { |
||||||
|
type: String, |
||||||
|
default: "" |
||||||
|
} |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
operationDate: getDay(0), |
||||||
|
chartInstances: [] |
||||||
|
}; |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
this.$nextTick(() => { |
||||||
|
this.renderCharts(); |
||||||
|
}); |
||||||
|
}, |
||||||
|
beforeDestroy() { |
||||||
|
this.chartInstances.forEach((chart) => { |
||||||
|
if (chart) chart.dispose(); |
||||||
|
}); |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
renderCharts() { |
||||||
|
// 销毁旧图表 |
||||||
|
this.chartInstances.forEach((chart) => { |
||||||
|
if (chart) chart.dispose(); |
||||||
|
}); |
||||||
|
this.chartInstances = []; |
||||||
|
|
||||||
|
// 渲染能耗占比饼图 |
||||||
|
if (this.$refs.energyPieChart) { |
||||||
|
const chart = echarts.init(this.$refs.energyPieChart); |
||||||
|
chart.setOption(this.getEnergyPieOption()); |
||||||
|
this.chartInstances.push(chart); |
||||||
|
} |
||||||
|
|
||||||
|
// 渲染能效趋势折线图 |
||||||
|
if (this.$refs.efficiencyLineChart) { |
||||||
|
const chart = echarts.init(this.$refs.efficiencyLineChart); |
||||||
|
chart.setOption(this.getEfficiencyLineOption()); |
||||||
|
this.chartInstances.push(chart); |
||||||
|
} |
||||||
|
|
||||||
|
// 渲染设备用电对比柱状图 |
||||||
|
if (this.$refs.deviceBarChart) { |
||||||
|
const chart = echarts.init(this.$refs.deviceBarChart); |
||||||
|
chart.setOption(this.getDeviceBarOption()); |
||||||
|
this.chartInstances.push(chart); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// 能耗占比饼图配置 |
||||||
|
getEnergyPieOption() { |
||||||
|
const width = this.$refs.energyPieChart?.clientWidth || 400; |
||||||
|
const titleFontSize = width / 50; |
||||||
|
|
||||||
|
return { |
||||||
|
title: { |
||||||
|
text: "各系统能耗占比", |
||||||
|
left: "center", |
||||||
|
textStyle: { |
||||||
|
fontSize: titleFontSize, |
||||||
|
color: "#333" |
||||||
|
} |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: "item", |
||||||
|
formatter: "{a} <br/>{b}: {c} ({d}%)" |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
orient: "vertical", |
||||||
|
left: "left", |
||||||
|
top: "middle" |
||||||
|
}, |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: "能耗占比", |
||||||
|
type: "pie", |
||||||
|
radius: "60%", |
||||||
|
center: ["50%", "50%"], |
||||||
|
data: [ |
||||||
|
{ value: 1200, name: "空调制冷系统" }, |
||||||
|
{ value: 800, name: "照明系统" }, |
||||||
|
{ value: 950, name: "水泵系统" }, |
||||||
|
{ value: 600, name: "热回收系统" }, |
||||||
|
{ value: 450, name: "其他" } |
||||||
|
], |
||||||
|
emphasis: { |
||||||
|
itemStyle: { |
||||||
|
shadowBlur: 10, |
||||||
|
shadowOffsetX: 0, |
||||||
|
shadowColor: "rgba(0, 0, 0, 0.5)" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
}, |
||||||
|
|
||||||
|
// 能效趋势折线图配置 |
||||||
|
getEfficiencyLineOption() { |
||||||
|
const width = this.$refs.efficiencyLineChart?.clientWidth || 400; |
||||||
|
const titleFontSize = width / 50; |
||||||
|
|
||||||
|
return { |
||||||
|
title: { |
||||||
|
text: "系统能效趋势", |
||||||
|
left: "center", |
||||||
|
textStyle: { |
||||||
|
fontSize: titleFontSize, |
||||||
|
color: "#333" |
||||||
|
} |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: "axis" |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
data: ["空调系统", "水泵系统", "整体能效"], |
||||||
|
top: "bottom" |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
left: "3%", |
||||||
|
right: "4%", |
||||||
|
bottom: "15%", |
||||||
|
containLabel: true |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: "category", |
||||||
|
boundaryGap: false, |
||||||
|
data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] |
||||||
|
}, |
||||||
|
yAxis: { |
||||||
|
type: "value", |
||||||
|
name: "能效比 (COP)" |
||||||
|
}, |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: "空调系统", |
||||||
|
type: "line", |
||||||
|
smooth: true, |
||||||
|
data: [5.2, 5.5, 5.8, 5.6, 5.9, 6.1, 6.0], |
||||||
|
areaStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: "rgba(64, 158, 255, 0.5)" }, |
||||||
|
{ offset: 1, color: "rgba(64, 158, 255, 0.05)" } |
||||||
|
]) |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "水泵系统", |
||||||
|
type: "line", |
||||||
|
smooth: true, |
||||||
|
data: [4.8, 5.0, 5.2, 5.1, 5.3, 5.5, 5.4], |
||||||
|
areaStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: "rgba(103, 194, 58, 0.5)" }, |
||||||
|
{ offset: 1, color: "rgba(103, 194, 58, 0.05)" } |
||||||
|
]) |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "整体能效", |
||||||
|
type: "line", |
||||||
|
smooth: true, |
||||||
|
data: [5.0, 5.3, 5.5, 5.4, 5.6, 5.8, 5.7], |
||||||
|
lineStyle: { |
||||||
|
width: 3, |
||||||
|
color: "#E6A23C" |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
}, |
||||||
|
|
||||||
|
// 设备用电对比柱状图配置 |
||||||
|
getDeviceBarOption() { |
||||||
|
const width = this.$refs.deviceBarChart?.clientWidth || 400; |
||||||
|
const titleFontSize = width / 50; |
||||||
|
|
||||||
|
return { |
||||||
|
title: { |
||||||
|
text: "主要设备用电对比", |
||||||
|
left: "center", |
||||||
|
textStyle: { |
||||||
|
fontSize: titleFontSize, |
||||||
|
color: "#333" |
||||||
|
} |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: "axis", |
||||||
|
axisPointer: { |
||||||
|
type: "shadow" |
||||||
|
} |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
data: ["用电量 (kWh)", "占比 (%)"], |
||||||
|
top: "bottom" |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
left: "3%", |
||||||
|
right: "4%", |
||||||
|
bottom: "15%", |
||||||
|
containLabel: true |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: "category", |
||||||
|
data: ["冷水机组", "冷冻泵", "冷却泵", "照明灯具", "水泵组", "其他设备"] |
||||||
|
}, |
||||||
|
yAxis: [ |
||||||
|
{ |
||||||
|
type: "value", |
||||||
|
name: "用电量 (kWh)", |
||||||
|
position: "left" |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "value", |
||||||
|
name: "占比 (%)", |
||||||
|
position: "right" |
||||||
|
} |
||||||
|
], |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: "用电量 (kWh)", |
||||||
|
type: "bar", |
||||||
|
barWidth: "40%", |
||||||
|
data: [850, 420, 380, 520, 680, 350], |
||||||
|
itemStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: "#83bff6" }, |
||||||
|
{ offset: 1, color: "#188df0" } |
||||||
|
]) |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "占比 (%)", |
||||||
|
type: "bar", |
||||||
|
yAxisIndex: 1, |
||||||
|
barWidth: "40%", |
||||||
|
data: [26.5, 13.1, 11.9, 16.2, 21.2, 10.9], |
||||||
|
itemStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: "#f093fb" }, |
||||||
|
{ offset: 1, color: "#f5576c" } |
||||||
|
]) |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
@import "~@/assets/styles/variables.scss"; |
||||||
|
|
||||||
|
.report-wrapper { |
||||||
|
background: #fff; |
||||||
|
padding: 0.2rem; |
||||||
|
border-radius: 4px; |
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); |
||||||
|
|
||||||
|
.report-title-section { |
||||||
|
text-align: center; |
||||||
|
border-bottom: 2px solid $blue; |
||||||
|
padding-bottom: 0.12rem; |
||||||
|
margin-bottom: 0.2rem; |
||||||
|
|
||||||
|
h1 { |
||||||
|
margin: 0 0 0.1rem 0; |
||||||
|
font-size: 0.2rem; |
||||||
|
color: $blue; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
.report-time { |
||||||
|
font-size: 0.12rem; |
||||||
|
color: #909399; |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.report-summary { |
||||||
|
background: #ecf5ff; |
||||||
|
padding: 0.12rem; |
||||||
|
border-radius: 3px; |
||||||
|
margin-bottom: 0.2rem; |
||||||
|
border-left: 4px solid $light-blue; |
||||||
|
|
||||||
|
h3 { |
||||||
|
margin: 0 0 0.08rem 0; |
||||||
|
font-size: 0.15rem; |
||||||
|
color: $light-blue; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
p { |
||||||
|
margin: 0; |
||||||
|
font-size: 0.13rem; |
||||||
|
line-height: 1.6; |
||||||
|
color: #606266; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.report-main { |
||||||
|
.report-section { |
||||||
|
margin-bottom: 0.25rem; |
||||||
|
|
||||||
|
h2 { |
||||||
|
font-size: 0.16rem; |
||||||
|
color: $light-blue; |
||||||
|
border-left: 4px solid $light-blue; |
||||||
|
padding-left: 0.1rem; |
||||||
|
margin-bottom: 0.12rem; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
.section-content { |
||||||
|
p { |
||||||
|
font-size: 0.13rem; |
||||||
|
line-height: 1.7; |
||||||
|
color: #606266; |
||||||
|
margin-bottom: 0.12rem; |
||||||
|
} |
||||||
|
|
||||||
|
.charts-container { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); |
||||||
|
gap: 0.15rem; |
||||||
|
margin-bottom: 0.12rem; |
||||||
|
|
||||||
|
.chart-box { |
||||||
|
width: 100%; |
||||||
|
height: 280px; |
||||||
|
background: #fafafa; |
||||||
|
border-radius: 3px; |
||||||
|
padding: 0.1rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.analysis-points { |
||||||
|
.analysis-point { |
||||||
|
display: flex; |
||||||
|
align-items: flex-start; |
||||||
|
gap: 0.06rem; |
||||||
|
margin-bottom: 0.08rem; |
||||||
|
font-size: 0.13rem; |
||||||
|
color: #606266; |
||||||
|
|
||||||
|
i { |
||||||
|
color: $green; |
||||||
|
font-size: 0.14rem; |
||||||
|
margin-top: 0.02rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.suggestions { |
||||||
|
margin-top: 0.12rem; |
||||||
|
padding: 0.12rem; |
||||||
|
background: #fef0f0; |
||||||
|
border-radius: 3px; |
||||||
|
border-left: 4px solid $red; |
||||||
|
|
||||||
|
h4 { |
||||||
|
margin: 0 0 0.08rem 0; |
||||||
|
font-size: 0.14rem; |
||||||
|
color: $red; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
ul { |
||||||
|
margin: 0; |
||||||
|
padding-left: 0.18rem; |
||||||
|
|
||||||
|
li { |
||||||
|
font-size: 0.13rem; |
||||||
|
line-height: 1.7; |
||||||
|
color: #606266; |
||||||
|
margin-bottom: 0.05rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.report-footer { |
||||||
|
border-top: 1px solid #e6e6e6; |
||||||
|
padding-top: 0.12rem; |
||||||
|
margin-top: 0.15rem; |
||||||
|
|
||||||
|
.footer-info { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
font-size: 0.11rem; |
||||||
|
color: #909399; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,625 @@ |
|||||||
|
<template> |
||||||
|
<div class="lighting-ai-saving report-wrapper"> |
||||||
|
<!-- 报表标题 --> |
||||||
|
<div class="report-title-section"> |
||||||
|
<h1>{{ reportData.title }}</h1> |
||||||
|
<p class="report-time">生成时间:{{ reportData.generateTime }}</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 报表摘要 --> |
||||||
|
<div class="report-summary"> |
||||||
|
<h3>AI照明节能策略报告</h3> |
||||||
|
<p>{{ reportData.summary }}</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 策略配置区域 --> |
||||||
|
<div class="strategy-config"> |
||||||
|
<h2>一、节能策略配置</h2> |
||||||
|
|
||||||
|
<!-- 目标选择 --> |
||||||
|
<div class="target-selection"> |
||||||
|
<h3>优化目标</h3> |
||||||
|
<el-radio-group v-model="selectedTarget" size="small"> |
||||||
|
<el-radio label="energy">节能优先</el-radio> |
||||||
|
<el-radio label="comfort">舒适优先</el-radio> |
||||||
|
<el-radio label="balanced">平衡模式</el-radio> |
||||||
|
</el-radio-group> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 区域选择 --> |
||||||
|
<div class="area-selection"> |
||||||
|
<h3>适用区域</h3> |
||||||
|
<el-checkbox-group v-model="selectedAreas"> |
||||||
|
<el-checkbox label="office">办公区</el-checkbox> |
||||||
|
<el-checkbox label="corridor">走廊</el-checkbox> |
||||||
|
<el-checkbox label="meeting">会议室</el-checkbox> |
||||||
|
<el-checkbox label="garage">车库</el-checkbox> |
||||||
|
</el-checkbox-group> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 执行模式 --> |
||||||
|
<div class="execution-mode"> |
||||||
|
<h3>执行方式</h3> |
||||||
|
<el-radio-group v-model="executionMode" size="small"> |
||||||
|
<el-radio label="auto">自动执行(动态调整)</el-radio> |
||||||
|
<el-radio label="suggestion">仅建议</el-radio> |
||||||
|
</el-radio-group> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 场景策略详情 --> |
||||||
|
<div class="scene-strategy"> |
||||||
|
<h2>二、场景化调光策略</h2> |
||||||
|
|
||||||
|
<el-tabs v-model="activeAreaTab" type="card"> |
||||||
|
<el-tab-pane |
||||||
|
v-for="area in areaConfig" |
||||||
|
:key="area.value" |
||||||
|
:label="area.label" |
||||||
|
:name="area.value" |
||||||
|
> |
||||||
|
<div class="area-strategy"> |
||||||
|
<h3>{{ area.label }}照明策略</h3> |
||||||
|
|
||||||
|
<!-- 亮度占比配置 --> |
||||||
|
<div class="brightness-config"> |
||||||
|
<h4>亮度模式占比</h4> |
||||||
|
<div class="brightness-sliders"> |
||||||
|
<div class="slider-item"> |
||||||
|
<label>低亮模式 ({{ area.brightness.low }}%)</label> |
||||||
|
<el-slider |
||||||
|
v-model="area.brightness.low" |
||||||
|
:min="0" |
||||||
|
:max="100" |
||||||
|
@change="updateBrightness(area)" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div class="slider-item"> |
||||||
|
<label>中亮模式 ({{ area.brightness.medium }}%)</label> |
||||||
|
<el-slider |
||||||
|
v-model="area.brightness.medium" |
||||||
|
:min="0" |
||||||
|
:max="100" |
||||||
|
@change="updateBrightness(area)" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div class="slider-item"> |
||||||
|
<label>高亮模式 ({{ area.brightness.high }}%)</label> |
||||||
|
<el-slider |
||||||
|
v-model="area.brightness.high" |
||||||
|
:min="0" |
||||||
|
:max="100" |
||||||
|
@change="updateBrightness(area)" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 感应触发时长 --> |
||||||
|
<div class="trigger-config"> |
||||||
|
<h4>感应触发设置</h4> |
||||||
|
<el-form :inline="true" size="small"> |
||||||
|
<el-form-item label="无人检测延时"> |
||||||
|
<el-input-number |
||||||
|
v-model="area.trigger.delay" |
||||||
|
:min="1" |
||||||
|
:max="60" |
||||||
|
controls-position="right" |
||||||
|
/> 分钟 |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="恢复亮度延时"> |
||||||
|
<el-input-number |
||||||
|
v-model="area.trigger.recovery" |
||||||
|
:min="1" |
||||||
|
:max="30" |
||||||
|
controls-position="right" |
||||||
|
/> 秒 |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 时间段策略 --> |
||||||
|
<div class="time-strategy"> |
||||||
|
<h4>时段策略</h4> |
||||||
|
<el-table :data="area.timeSlots" size="small" border> |
||||||
|
<el-table-column prop="timeRange" label="时间段" width="120"/> |
||||||
|
<el-table-column prop="brightnessLevel" label="亮度等级" width="100"> |
||||||
|
<template slot-scope="{row}"> |
||||||
|
<el-select v-model="row.brightnessLevel" size="small"> |
||||||
|
<el-option label="低亮" value="low"/> |
||||||
|
<el-option label="中亮" value="medium"/> |
||||||
|
<el-option label="高亮" value="high"/> |
||||||
|
</el-select> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column prop="occupancySensitivity" label="人员感应灵敏度" width="140"> |
||||||
|
<template slot-scope="{row}"> |
||||||
|
<el-select v-model="row.occupancySensitivity" size="small"> |
||||||
|
<el-option label="高" value="high"/> |
||||||
|
<el-option label="中" value="medium"/> |
||||||
|
<el-option label="低" value="low"/> |
||||||
|
</el-select> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
<el-table-column label="操作" width="80"> |
||||||
|
<template slot-scope="{row, $index}"> |
||||||
|
<el-button |
||||||
|
type="text" |
||||||
|
size="small" |
||||||
|
@click="removeTimeSlot(area, $index)" |
||||||
|
>删除</el-button> |
||||||
|
</template> |
||||||
|
</el-table-column> |
||||||
|
</el-table> |
||||||
|
<el-button |
||||||
|
type="primary" |
||||||
|
size="small" |
||||||
|
icon="el-icon-plus" |
||||||
|
style="margin-top: 10px;" |
||||||
|
@click="addTimeSlot(area)" |
||||||
|
>添加时段</el-button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</el-tab-pane> |
||||||
|
</el-tabs> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 效果预览与评估 --> |
||||||
|
<div class="effect-preview"> |
||||||
|
<h2>三、策略效果预览</h2> |
||||||
|
|
||||||
|
<div class="preview-metrics"> |
||||||
|
<div class="metric-card"> |
||||||
|
<div class="metric-value">{{ estimatedSaving }}%</div> |
||||||
|
<div class="metric-label">预计节能率</div> |
||||||
|
</div> |
||||||
|
<div class="metric-card"> |
||||||
|
<div class="metric-value">{{ comfortScore }}</div> |
||||||
|
<div class="metric-label">舒适度评分</div> |
||||||
|
</div> |
||||||
|
<div class="metric-card"> |
||||||
|
<div class="metric-value">{{ implementationCost }}</div> |
||||||
|
<div class="metric-label">实施成本</div> |
||||||
|
</div> |
||||||
|
<div class="metric-card"> |
||||||
|
<div class="metric-value">{{ paybackPeriod }}</div> |
||||||
|
<div class="metric-label">投资回收期(月)</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 节能效果图表 --> |
||||||
|
<div class="charts-container"> |
||||||
|
<div ref="savingTrendChart" class="chart-box"></div> |
||||||
|
<div ref="areaComparisonChart" class="chart-box"></div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 操作按钮 --> |
||||||
|
<div class="action-buttons"> |
||||||
|
<el-button type="primary" @click="saveStrategy">保存策略</el-button> |
||||||
|
<el-button type="success" @click="executeStrategy">立即执行</el-button> |
||||||
|
<el-button @click="resetStrategy">重置</el-button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import * as echarts from 'echarts'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'LightingAISaving', |
||||||
|
props: { |
||||||
|
reportData: { |
||||||
|
type: Object, |
||||||
|
required: true |
||||||
|
}, |
||||||
|
userName: { |
||||||
|
type: String, |
||||||
|
default: '' |
||||||
|
} |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
selectedTarget: 'balanced', |
||||||
|
selectedAreas: ['office', 'corridor', 'meeting', 'garage'], |
||||||
|
executionMode: 'auto', |
||||||
|
activeAreaTab: 'office', |
||||||
|
|
||||||
|
// 区域配置数据 |
||||||
|
areaConfig: [ |
||||||
|
{ |
||||||
|
value: 'office', |
||||||
|
label: '办公区', |
||||||
|
brightness: { low: 20, medium: 50, high: 80 }, |
||||||
|
trigger: { delay: 15, recovery: 10 }, |
||||||
|
timeSlots: [ |
||||||
|
{ timeRange: '08:00-12:00', brightnessLevel: 'high', occupancySensitivity: 'high' }, |
||||||
|
{ timeRange: '12:00-13:00', brightnessLevel: 'medium', occupancySensitivity: 'medium' }, |
||||||
|
{ timeRange: '13:00-18:00', brightnessLevel: 'high', occupancySensitivity: 'high' }, |
||||||
|
{ timeRange: '18:00-22:00', brightnessLevel: 'low', occupancySensitivity: 'low' } |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: 'corridor', |
||||||
|
label: '走廊', |
||||||
|
brightness: { low: 10, medium: 30, high: 60 }, |
||||||
|
trigger: { delay: 5, recovery: 5 }, |
||||||
|
timeSlots: [ |
||||||
|
{ timeRange: '06:00-22:00', brightnessLevel: 'medium', occupancySensitivity: 'high' }, |
||||||
|
{ timeRange: '22:00-06:00', brightnessLevel: 'low', occupancySensitivity: 'low' } |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: 'meeting', |
||||||
|
label: '会议室', |
||||||
|
brightness: { low: 15, medium: 40, high: 75 }, |
||||||
|
trigger: { delay: 30, recovery: 15 }, |
||||||
|
timeSlots: [ |
||||||
|
{ timeRange: '08:00-18:00', brightnessLevel: 'medium', occupancySensitivity: 'medium' }, |
||||||
|
{ timeRange: '18:00-22:00', brightnessLevel: 'low', occupancySensitivity: 'low' } |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: 'garage', |
||||||
|
label: '车库', |
||||||
|
brightness: { low: 5, medium: 25, high: 50 }, |
||||||
|
trigger: { delay: 10, recovery: 8 }, |
||||||
|
timeSlots: [ |
||||||
|
{ timeRange: '06:00-22:00', brightnessLevel: 'medium', occupancySensitivity: 'high' }, |
||||||
|
{ timeRange: '22:00-06:00', brightnessLevel: 'low', occupancySensitivity: 'low' } |
||||||
|
] |
||||||
|
} |
||||||
|
], |
||||||
|
|
||||||
|
// 效果预览数据 |
||||||
|
estimatedSaving: 45, |
||||||
|
comfortScore: 8.2, |
||||||
|
implementationCost: '低', |
||||||
|
paybackPeriod: 6 |
||||||
|
}; |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
this.initCharts(); |
||||||
|
}, |
||||||
|
beforeDestroy() { |
||||||
|
this.disposeCharts(); |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
initCharts() { |
||||||
|
// 节能趋势图 |
||||||
|
if (this.$refs.savingTrendChart) { |
||||||
|
const savingTrendChart = echarts.init(this.$refs.savingTrendChart); |
||||||
|
savingTrendChart.setOption({ |
||||||
|
title: { text: '月度节能效果趋势', left: 'center' }, |
||||||
|
tooltip: { trigger: 'axis' }, |
||||||
|
xAxis: { type: 'category', data: ['1月', '2月', '3月', '4月', '5月', '6月'] }, |
||||||
|
yAxis: { type: 'value', name: '节能率(%)' }, |
||||||
|
series: [{ |
||||||
|
name: '节能率', |
||||||
|
type: 'line', |
||||||
|
data: [30, 35, 40, 42, 45, 48], |
||||||
|
smooth: true, |
||||||
|
lineStyle: { width: 3 }, |
||||||
|
itemStyle: { color: '#0ac1c7' } |
||||||
|
}] |
||||||
|
}); |
||||||
|
this.savingTrendChart = savingTrendChart; |
||||||
|
} |
||||||
|
|
||||||
|
// 区域对比图 |
||||||
|
if (this.$refs.areaComparisonChart) { |
||||||
|
const areaComparisonChart = echarts.init(this.$refs.areaComparisonChart); |
||||||
|
areaComparisonChart.setOption({ |
||||||
|
title: { text: '各区域节能效果对比', left: 'center' }, |
||||||
|
tooltip: { trigger: 'item' }, |
||||||
|
legend: { top: 'bottom' }, |
||||||
|
series: [{ |
||||||
|
name: '节能效果', |
||||||
|
type: 'pie', |
||||||
|
radius: ['40%', '70%'], |
||||||
|
avoidLabelOverlap: false, |
||||||
|
itemStyle: { |
||||||
|
borderRadius: 10, |
||||||
|
borderColor: '#fff', |
||||||
|
borderWidth: 2 |
||||||
|
}, |
||||||
|
label: { show: false, position: 'center' }, |
||||||
|
emphasis: { |
||||||
|
label: { show: true, fontSize: '16', fontWeight: 'bold' } |
||||||
|
}, |
||||||
|
labelLine: { show: false }, |
||||||
|
data: [ |
||||||
|
{ value: 35, name: '办公区' }, |
||||||
|
{ value: 25, name: '走廊' }, |
||||||
|
{ value: 20, name: '会议室' }, |
||||||
|
{ value: 20, name: '车库' } |
||||||
|
] |
||||||
|
}] |
||||||
|
}); |
||||||
|
this.areaComparisonChart = areaComparisonChart; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
disposeCharts() { |
||||||
|
if (this.savingTrendChart) { |
||||||
|
this.savingTrendChart.dispose(); |
||||||
|
this.savingTrendChart = null; |
||||||
|
} |
||||||
|
if (this.areaComparisonChart) { |
||||||
|
this.areaComparisonChart.dispose(); |
||||||
|
this.areaComparisonChart = null; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
updateBrightness(area) { |
||||||
|
// 确保三个亮度值总和不超过100% |
||||||
|
const total = area.brightness.low + area.brightness.medium + area.brightness.high; |
||||||
|
if (total > 100) { |
||||||
|
// 按比例调整 |
||||||
|
const ratio = 100 / total; |
||||||
|
area.brightness.low = Math.round(area.brightness.low * ratio); |
||||||
|
area.brightness.medium = Math.round(area.brightness.medium * ratio); |
||||||
|
area.brightness.high = Math.round(area.brightness.high * ratio); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
addTimeSlot(area) { |
||||||
|
area.timeSlots.push({ |
||||||
|
timeRange: '00:00-24:00', |
||||||
|
brightnessLevel: 'medium', |
||||||
|
occupancySensitivity: 'medium' |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
removeTimeSlot(area, index) { |
||||||
|
if (area.timeSlots.length > 1) { |
||||||
|
area.timeSlots.splice(index, 1); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
saveStrategy() { |
||||||
|
this.$message.success('策略保存成功!'); |
||||||
|
// 这里可以调用API保存策略 |
||||||
|
}, |
||||||
|
|
||||||
|
executeStrategy() { |
||||||
|
this.$confirm('确定要立即执行此AI节能策略吗?', '确认执行', { |
||||||
|
confirmButtonText: '确定执行', |
||||||
|
cancelButtonText: '取消', |
||||||
|
type: 'warning' |
||||||
|
}).then(() => { |
||||||
|
this.$message.success('策略已开始执行!'); |
||||||
|
// 这里可以调用API执行策略 |
||||||
|
}).catch(() => { |
||||||
|
// 取消操作 |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
resetStrategy() { |
||||||
|
this.$confirm('确定要重置所有策略配置吗?', '确认重置', { |
||||||
|
confirmButtonText: '确定重置', |
||||||
|
cancelButtonText: '取消', |
||||||
|
type: 'info' |
||||||
|
}).then(() => { |
||||||
|
// 重置到默认配置 |
||||||
|
this.areaConfig = JSON.parse(JSON.stringify([ |
||||||
|
{ |
||||||
|
value: 'office', |
||||||
|
label: '办公区', |
||||||
|
brightness: { low: 20, medium: 50, high: 80 }, |
||||||
|
trigger: { delay: 15, recovery: 10 }, |
||||||
|
timeSlots: [ |
||||||
|
{ timeRange: '08:00-12:00', brightnessLevel: 'high', occupancySensitivity: 'high' }, |
||||||
|
{ timeRange: '12:00-13:00', brightnessLevel: 'medium', occupancySensitivity: 'medium' }, |
||||||
|
{ timeRange: '13:00-18:00', brightnessLevel: 'high', occupancySensitivity: 'high' }, |
||||||
|
{ timeRange: '18:00-22:00', brightnessLevel: 'low', occupancySensitivity: 'low' } |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: 'corridor', |
||||||
|
label: '走廊', |
||||||
|
brightness: { low: 10, medium: 30, high: 60 }, |
||||||
|
trigger: { delay: 5, recovery: 5 }, |
||||||
|
timeSlots: [ |
||||||
|
{ timeRange: '06:00-22:00', brightnessLevel: 'medium', occupancySensitivity: 'high' }, |
||||||
|
{ timeRange: '22:00-06:00', brightnessLevel: 'low', occupancySensitivity: 'low' } |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: 'meeting', |
||||||
|
label: '会议室', |
||||||
|
brightness: { low: 15, medium: 40, high: 75 }, |
||||||
|
trigger: { delay: 30, recovery: 15 }, |
||||||
|
timeSlots: [ |
||||||
|
{ timeRange: '08:00-18:00', brightnessLevel: 'medium', occupancySensitivity: 'medium' }, |
||||||
|
{ timeRange: '18:00-22:00', brightnessLevel: 'low', occupancySensitivity: 'low' } |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
value: 'garage', |
||||||
|
label: '车库', |
||||||
|
brightness: { low: 5, medium: 25, high: 50 }, |
||||||
|
trigger: { delay: 10, recovery: 8 }, |
||||||
|
timeSlots: [ |
||||||
|
{ timeRange: '06:00-22:00', brightnessLevel: 'medium', occupancySensitivity: 'high' }, |
||||||
|
{ timeRange: '22:00-06:00', brightnessLevel: 'low', occupancySensitivity: 'low' } |
||||||
|
] |
||||||
|
} |
||||||
|
])); |
||||||
|
this.$message.success('策略已重置!'); |
||||||
|
}).catch(() => { |
||||||
|
// 取消操作 |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.lighting-ai-saving { |
||||||
|
.strategy-config { |
||||||
|
margin-bottom: 20px; |
||||||
|
|
||||||
|
h3 { |
||||||
|
margin: 15px 0 10px 0; |
||||||
|
color: #0ac1c7; |
||||||
|
} |
||||||
|
|
||||||
|
.el-radio-group, |
||||||
|
.el-checkbox-group { |
||||||
|
display: flex; |
||||||
|
flex-wrap: wrap; |
||||||
|
gap: 15px; |
||||||
|
} |
||||||
|
|
||||||
|
.el-radio, |
||||||
|
.el-checkbox { |
||||||
|
margin-right: 20px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.scene-strategy { |
||||||
|
margin-bottom: 20px; |
||||||
|
|
||||||
|
.area-strategy { |
||||||
|
padding: 15px; |
||||||
|
background: rgba(255, 255, 255, 0.05); |
||||||
|
border-radius: 8px; |
||||||
|
|
||||||
|
h3 { |
||||||
|
margin-bottom: 15px; |
||||||
|
color: #0ac1c7; |
||||||
|
} |
||||||
|
|
||||||
|
.brightness-config { |
||||||
|
margin-bottom: 20px; |
||||||
|
|
||||||
|
h4 { |
||||||
|
margin-bottom: 10px; |
||||||
|
color: #e0e6ed; |
||||||
|
} |
||||||
|
|
||||||
|
.brightness-sliders { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
gap: 15px; |
||||||
|
|
||||||
|
.slider-item { |
||||||
|
label { |
||||||
|
display: block; |
||||||
|
margin-bottom: 5px; |
||||||
|
font-size: 14px; |
||||||
|
color: #a0b3c6; |
||||||
|
} |
||||||
|
|
||||||
|
::v-deep .el-slider { |
||||||
|
.el-slider__runway { |
||||||
|
height: 6px; |
||||||
|
background: #2d3f52; |
||||||
|
} |
||||||
|
|
||||||
|
.el-slider__bar { |
||||||
|
height: 6px; |
||||||
|
background: #0ac1c7; |
||||||
|
} |
||||||
|
|
||||||
|
.el-slider__button { |
||||||
|
width: 16px; |
||||||
|
height: 16px; |
||||||
|
border: 2px solid #0ac1c7; |
||||||
|
background: #1a2a3a; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.trigger-config { |
||||||
|
margin-bottom: 20px; |
||||||
|
|
||||||
|
h4 { |
||||||
|
margin-bottom: 10px; |
||||||
|
color: #e0e6ed; |
||||||
|
} |
||||||
|
|
||||||
|
::v-deep .el-form-item { |
||||||
|
margin-right: 20px; |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.time-strategy { |
||||||
|
h4 { |
||||||
|
margin-bottom: 10px; |
||||||
|
color: #e0e6ed; |
||||||
|
} |
||||||
|
|
||||||
|
::v-deep .el-table { |
||||||
|
.el-table__header th { |
||||||
|
background: #2d3f52; |
||||||
|
color: #e0e6ed; |
||||||
|
} |
||||||
|
|
||||||
|
.el-table__body td { |
||||||
|
background: #1a2a3a; |
||||||
|
color: #e0e6ed; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.effect-preview { |
||||||
|
margin-bottom: 20px; |
||||||
|
|
||||||
|
.preview-metrics { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
||||||
|
gap: 15px; |
||||||
|
margin-bottom: 20px; |
||||||
|
|
||||||
|
.metric-card { |
||||||
|
background: rgba(10, 193, 199, 0.1); |
||||||
|
border: 1px solid rgba(10, 193, 199, 0.3); |
||||||
|
border-radius: 8px; |
||||||
|
padding: 15px; |
||||||
|
text-align: center; |
||||||
|
|
||||||
|
.metric-value { |
||||||
|
font-size: 24px; |
||||||
|
font-weight: bold; |
||||||
|
color: #0ac1c7; |
||||||
|
margin-bottom: 5px; |
||||||
|
} |
||||||
|
|
||||||
|
.metric-label { |
||||||
|
font-size: 14px; |
||||||
|
color: #a0b3c6; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.charts-container { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: 1fr 1fr; |
||||||
|
gap: 20px; |
||||||
|
|
||||||
|
.chart-box { |
||||||
|
height: 300px; |
||||||
|
background: #1a2a3a; |
||||||
|
border-radius: 8px; |
||||||
|
padding: 15px; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.action-buttons { |
||||||
|
text-align: center; |
||||||
|
padding: 20px 0; |
||||||
|
|
||||||
|
.el-button { |
||||||
|
margin: 0 10px; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,467 @@ |
|||||||
|
<template> |
||||||
|
<div class="lighting-report report-wrapper"> |
||||||
|
<!-- 报表标题 --> |
||||||
|
<div class="report-title-section"> |
||||||
|
<h1>{{ reportData.title }}</h1> |
||||||
|
<p class="report-time">生成时间:{{ reportData.generateTime }}</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 报表摘要 --> |
||||||
|
<div class="report-summary"> |
||||||
|
<h3>报告摘要</h3> |
||||||
|
<p>{{ reportData.summary }}</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 报表主体内容 --> |
||||||
|
<div class="report-main"> |
||||||
|
<!-- 项目情况 --> |
||||||
|
<section class="report-section"> |
||||||
|
<h2>一、项目情况</h2> |
||||||
|
<div class="section-content"> |
||||||
|
<p>{{ reportData.projectInfo.description }}</p> |
||||||
|
<el-table |
||||||
|
v-if="reportData.projectInfo.configTable" |
||||||
|
:data="reportData.projectInfo.configTable" |
||||||
|
border |
||||||
|
size="small" |
||||||
|
> |
||||||
|
<el-table-column |
||||||
|
v-for="col in reportData.projectInfo.configTableColumns" |
||||||
|
:key="col.prop" |
||||||
|
:prop="col.prop" |
||||||
|
:label="col.label" |
||||||
|
/> |
||||||
|
</el-table> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<!-- 运行数据 --> |
||||||
|
<section class="report-section"> |
||||||
|
<h2>二、运行数据</h2> |
||||||
|
<div class="section-content"> |
||||||
|
<div class="charts-container"> |
||||||
|
<!-- 节电量趋势图 (双维度) --> |
||||||
|
<div ref="energySavingTrendChart" class="chart-box"></div> |
||||||
|
<!-- 节能率趋势图 --> |
||||||
|
<div ref="savingRateChart" class="chart-box"></div> |
||||||
|
</div> |
||||||
|
<el-table |
||||||
|
v-if="reportData.operationData.dataTable" |
||||||
|
:data="reportData.operationData.dataTable" |
||||||
|
border |
||||||
|
stripe |
||||||
|
size="small" |
||||||
|
> |
||||||
|
<el-table-column |
||||||
|
v-for="col in reportData.operationData.tableColumns" |
||||||
|
:key="col.prop" |
||||||
|
:prop="col.prop" |
||||||
|
:label="col.label" |
||||||
|
/> |
||||||
|
</el-table> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
|
||||||
|
<!-- 分析总结 --> |
||||||
|
<section class="report-section"> |
||||||
|
<h2>三、分析总结</h2> |
||||||
|
<div class="section-content"> |
||||||
|
<div class="analysis-points"> |
||||||
|
<div |
||||||
|
v-for="(point, index) in reportData.analysisSummary.points" |
||||||
|
:key="index" |
||||||
|
class="analysis-point" |
||||||
|
> |
||||||
|
<i class="el-icon-caret-right"></i> |
||||||
|
<span>{{ point }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div v-if="reportData.analysisSummary.suggestions" class="suggestions"> |
||||||
|
<h4>优化建议:</h4> |
||||||
|
<ul> |
||||||
|
<li |
||||||
|
v-for="(suggestion, index) in reportData.analysisSummary.suggestions" |
||||||
|
:key="index" |
||||||
|
> |
||||||
|
{{ suggestion }} |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- 报表底部信息 --> |
||||||
|
<div class="report-footer"> |
||||||
|
<div class="footer-info"> |
||||||
|
<span>操作员:{{ userName }}</span> |
||||||
|
<span>生成日期:{{ operationDate }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import * as echarts from "echarts"; |
||||||
|
import { format, getDay } from "@/utils/datetime"; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "LightingReport", |
||||||
|
props: { |
||||||
|
reportData: { |
||||||
|
type: Object, |
||||||
|
default: () => ({}) |
||||||
|
}, |
||||||
|
userName: { |
||||||
|
type: String, |
||||||
|
default: "" |
||||||
|
} |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
operationDate: getDay(0), |
||||||
|
chartInstances: [] |
||||||
|
}; |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
this.$nextTick(() => { |
||||||
|
this.renderCharts(); |
||||||
|
}); |
||||||
|
}, |
||||||
|
beforeDestroy() { |
||||||
|
this.chartInstances.forEach((chart) => { |
||||||
|
if (chart) chart.dispose(); |
||||||
|
}); |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
renderCharts() { |
||||||
|
// 销毁旧图表 |
||||||
|
this.chartInstances.forEach((chart) => { |
||||||
|
if (chart) chart.dispose(); |
||||||
|
}); |
||||||
|
this.chartInstances = []; |
||||||
|
|
||||||
|
// 渲染节电量趋势图 (双维度) |
||||||
|
if (this.$refs.energySavingTrendChart) { |
||||||
|
const chart = echarts.init(this.$refs.energySavingTrendChart); |
||||||
|
chart.setOption(this.getEnergySavingTrendOption()); |
||||||
|
this.chartInstances.push(chart); |
||||||
|
} |
||||||
|
|
||||||
|
// 渲染节能率趋势图 |
||||||
|
if (this.$refs.savingRateChart) { |
||||||
|
const chart = echarts.init(this.$refs.savingRateChart); |
||||||
|
chart.setOption(this.getSavingRateOption()); |
||||||
|
this.chartInstances.push(chart); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// 节电量趋势图配置 (双维度) |
||||||
|
getEnergySavingTrendOption() { |
||||||
|
const width = this.$refs.energySavingTrendChart?.clientWidth || 400; |
||||||
|
const titleFontSize = width / 50; |
||||||
|
|
||||||
|
return { |
||||||
|
title: { |
||||||
|
text: "节电量趋势分析", |
||||||
|
left: "center", |
||||||
|
textStyle: { |
||||||
|
fontSize: titleFontSize, |
||||||
|
color: "#333" |
||||||
|
} |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: "axis", |
||||||
|
axisPointer: { |
||||||
|
type: "shadow" |
||||||
|
} |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
data: ["节能数 (盏)", "节电量 (kWh)"], |
||||||
|
top: "bottom" |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
left: "3%", |
||||||
|
right: "4%", |
||||||
|
bottom: "15%", |
||||||
|
containLabel: true |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: "category", |
||||||
|
data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] |
||||||
|
}, |
||||||
|
yAxis: [ |
||||||
|
{ |
||||||
|
type: "value", |
||||||
|
name: "节能数 (盏)", |
||||||
|
position: "left" |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "value", |
||||||
|
name: "节电量 (kWh)", |
||||||
|
position: "right" |
||||||
|
} |
||||||
|
], |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: "节能数 (盏)", |
||||||
|
type: "bar", |
||||||
|
barWidth: "35%", |
||||||
|
data: [125, 132, 118, 145, 138, 95, 88], |
||||||
|
itemStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: "#f093fb" }, |
||||||
|
{ offset: 1, color: "#f5576c" } |
||||||
|
]) |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "节电量 (kWh)", |
||||||
|
type: "bar", |
||||||
|
barWidth: "35%", |
||||||
|
yAxisIndex: 1, |
||||||
|
data: [450, 478, 425, 520, 495, 340, 315], |
||||||
|
itemStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: "#83bff6" }, |
||||||
|
{ offset: 1, color: "#188df0" } |
||||||
|
]) |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
}, |
||||||
|
|
||||||
|
// 节能率趋势图配置 |
||||||
|
getSavingRateOption() { |
||||||
|
const width = this.$refs.savingRateChart?.clientWidth || 400; |
||||||
|
const titleFontSize = width / 50; |
||||||
|
|
||||||
|
return { |
||||||
|
title: { |
||||||
|
text: "节能率趋势分析", |
||||||
|
left: "center", |
||||||
|
textStyle: { |
||||||
|
fontSize: titleFontSize, |
||||||
|
color: "#333" |
||||||
|
} |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
trigger: "axis" |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
data: ["当日节能率", "环比变化", "同比变化"], |
||||||
|
top: "bottom" |
||||||
|
}, |
||||||
|
grid: { |
||||||
|
left: "3%", |
||||||
|
right: "4%", |
||||||
|
bottom: "15%", |
||||||
|
containLabel: true |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
type: "category", |
||||||
|
boundaryGap: false, |
||||||
|
data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] |
||||||
|
}, |
||||||
|
yAxis: { |
||||||
|
type: "value", |
||||||
|
name: "节能率 (%)", |
||||||
|
axisLabel: { |
||||||
|
formatter: "{value}%" |
||||||
|
} |
||||||
|
}, |
||||||
|
series: [ |
||||||
|
{ |
||||||
|
name: "当日节能率", |
||||||
|
type: "line", |
||||||
|
smooth: true, |
||||||
|
data: [32.5, 34.2, 31.8, 35.6, 33.9, 28.5, 27.2], |
||||||
|
lineStyle: { |
||||||
|
width: 3, |
||||||
|
color: "#67C23A" |
||||||
|
}, |
||||||
|
areaStyle: { |
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
||||||
|
{ offset: 0, color: "rgba(103, 194, 58, 0.5)" }, |
||||||
|
{ offset: 1, color: "rgba(103, 194, 58, 0.05)" } |
||||||
|
]) |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "环比变化", |
||||||
|
type: "line", |
||||||
|
smooth: true, |
||||||
|
data: [1.2, 1.7, -2.4, 3.8, -1.7, -5.4, -1.3], |
||||||
|
lineStyle: { |
||||||
|
width: 2, |
||||||
|
color: "#E6A23C", |
||||||
|
type: "dashed" |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "同比变化", |
||||||
|
type: "line", |
||||||
|
smooth: true, |
||||||
|
data: [3.5, 4.2, 2.8, 5.1, 3.9, 1.5, 0.8], |
||||||
|
lineStyle: { |
||||||
|
width: 2, |
||||||
|
color: "#409EFF", |
||||||
|
type: "dotted" |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
@import "~@/assets/styles/variables.scss"; |
||||||
|
|
||||||
|
.report-wrapper { |
||||||
|
background: #fff; |
||||||
|
padding: 0.2rem; |
||||||
|
border-radius: 4px; |
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); |
||||||
|
|
||||||
|
.report-title-section { |
||||||
|
text-align: center; |
||||||
|
border-bottom: 2px solid $blue; |
||||||
|
padding-bottom: 0.12rem; |
||||||
|
margin-bottom: 0.2rem; |
||||||
|
|
||||||
|
h1 { |
||||||
|
margin: 0 0 0.1rem 0; |
||||||
|
font-size: 0.2rem; |
||||||
|
color: $blue; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
.report-time { |
||||||
|
font-size: 0.12rem; |
||||||
|
color: #909399; |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.report-summary { |
||||||
|
background: #ecf5ff; |
||||||
|
padding: 0.12rem; |
||||||
|
border-radius: 3px; |
||||||
|
margin-bottom: 0.2rem; |
||||||
|
border-left: 4px solid $light-blue; |
||||||
|
|
||||||
|
h3 { |
||||||
|
margin: 0 0 0.08rem 0; |
||||||
|
font-size: 0.15rem; |
||||||
|
color: $light-blue; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
p { |
||||||
|
margin: 0; |
||||||
|
font-size: 0.13rem; |
||||||
|
line-height: 1.6; |
||||||
|
color: #606266; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.report-main { |
||||||
|
.report-section { |
||||||
|
margin-bottom: 0.25rem; |
||||||
|
|
||||||
|
h2 { |
||||||
|
font-size: 0.16rem; |
||||||
|
color: $light-blue; |
||||||
|
border-left: 4px solid $light-blue; |
||||||
|
padding-left: 0.1rem; |
||||||
|
margin-bottom: 0.12rem; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
.section-content { |
||||||
|
p { |
||||||
|
font-size: 0.13rem; |
||||||
|
line-height: 1.7; |
||||||
|
color: #606266; |
||||||
|
margin-bottom: 0.12rem; |
||||||
|
} |
||||||
|
|
||||||
|
.charts-container { |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); |
||||||
|
gap: 0.15rem; |
||||||
|
margin-bottom: 0.12rem; |
||||||
|
|
||||||
|
.chart-box { |
||||||
|
width: 100%; |
||||||
|
height: 280px; |
||||||
|
background: #fafafa; |
||||||
|
border-radius: 3px; |
||||||
|
padding: 0.1rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.analysis-points { |
||||||
|
.analysis-point { |
||||||
|
display: flex; |
||||||
|
align-items: flex-start; |
||||||
|
gap: 0.06rem; |
||||||
|
margin-bottom: 0.08rem; |
||||||
|
font-size: 0.13rem; |
||||||
|
color: #606266; |
||||||
|
|
||||||
|
i { |
||||||
|
color: $green; |
||||||
|
font-size: 0.14rem; |
||||||
|
margin-top: 0.02rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.suggestions { |
||||||
|
margin-top: 0.12rem; |
||||||
|
padding: 0.12rem; |
||||||
|
background: #fef0f0; |
||||||
|
border-radius: 3px; |
||||||
|
border-left: 4px solid $red; |
||||||
|
|
||||||
|
h4 { |
||||||
|
margin: 0 0 0.08rem 0; |
||||||
|
font-size: 0.14rem; |
||||||
|
color: $red; |
||||||
|
font-weight: 500; |
||||||
|
} |
||||||
|
|
||||||
|
ul { |
||||||
|
margin: 0; |
||||||
|
padding-left: 0.18rem; |
||||||
|
|
||||||
|
li { |
||||||
|
font-size: 0.13rem; |
||||||
|
line-height: 1.7; |
||||||
|
color: #606266; |
||||||
|
margin-bottom: 0.05rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.report-footer { |
||||||
|
border-top: 1px solid #e6e6e6; |
||||||
|
padding-top: 0.12rem; |
||||||
|
margin-top: 0.15rem; |
||||||
|
|
||||||
|
.footer-info { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
font-size: 0.11rem; |
||||||
|
color: #909399; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||