Browse Source

添加ai助手以及对话生成报表

bl_ai
25604 2 weeks ago
parent
commit
2c8e50d253
  1. 207
      src/App.vue
  2. 208
      src/api/ai.js
  3. 8
      src/router/index.js
  4. 472
      src/views/ai/index.vue
  5. 686
      src/views/index.vue
  6. 693
      src/views/index_ers.vue

207
src/App.vue

@ -2,34 +2,87 @@
<div id="app"> <div id="app">
<router-view /> <router-view />
<theme-picker /> <theme-picker />
<div
v-if="isLoggedIn"
class="ai-robot-float"
ref="robotFloat"
@mousedown="startDrag"
@dblclick="handleDoubleClick"
:style="{ left: robotPosition.x + 'px', top: robotPosition.y + 'px' }"
>
<img src="@/assets/logo/logo.png" alt="节能岛AI助手" />
</div>
<el-dialog
title="节能岛AI助手"
:visible.sync="showAIDialog"
:close-on-click-modal="false"
:close-on-press-escape="true"
width="50vw"
top="25vh"
custom-class="ai-dialog"
>
<div class="ai-dialog-content">
<component :is="AIComponent" @close="showAIDialog = false" />
</div>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import ThemePicker from "@/components/ThemePicker"; import ThemePicker from "@/components/ThemePicker";
import { exitFullscreen, isFullscreen } from "@/utils/fullscreen"; import { exitFullscreen, isFullscreen } from "@/utils/fullscreen";
import { mapState } from "vuex";
export default { export default {
name: "App", name: "App",
components: { ThemePicker }, components: { ThemePicker },
metaInfo() { data() {
return { return {
title: showAIDialog: false,
this.$store.state.settings.dynamicTitle && AIComponent: null,
this.$store.state.settings.title, robotPosition: { x: window.innerWidth - 90, y: window.innerHeight - 140 },
titleTemplate: (title) => { isDragging: false,
return title dragOffset: { x: 0, y: 0 },
? `${title} - ${process.env.VUE_APP_TITLE}` hasMoved: false,
: process.env.VUE_APP_TITLE; startX: 0,
}, startY: 0,
}; };
}, },
computed: {
...mapState({
token: state => state.user.token,
}),
isLoggedIn() {
return !!this.token;
}
},
watch: {
showAIDialog(newVal) {
if (newVal && !this.AIComponent) {
// 使
import("@/views/ai/index").then((module) => {
this.AIComponent = module.default;
console.log("AI 组件加载成功", module);
}).catch((err) => {
console.error("AI 组件加载失败", err);
});
}
},
},
mounted() { mounted() {
// Esc 退 // Esc 退
window.addEventListener("keydown", this.handleKeyDown); window.addEventListener("keydown", this.handleKeyDown);
//
window.addEventListener("mousemove", this.onDrag);
window.addEventListener("mouseup", this.endDrag);
//
window.addEventListener("resize", this.handleResize);
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener("keydown", this.handleKeyDown); window.removeEventListener("keydown", this.handleKeyDown);
window.removeEventListener("mousemove", this.onDrag);
window.removeEventListener("mouseup", this.endDrag);
window.removeEventListener("resize", this.handleResize);
}, },
methods: { methods: {
handleKeyDown(event) { handleKeyDown(event) {
@ -37,6 +90,66 @@ export default {
exitFullscreen(); exitFullscreen();
} }
}, },
handleClick() {
//
},
handleDoubleClick() {
//
if (!this.hasMoved) {
this.showAIDialog = true;
}
},
startDrag(event) {
if (event.button !== 0) return; //
event.preventDefault(); //
event.stopPropagation(); //
this.isDragging = true;
this.hasMoved = false;
this.dragOffset.x = event.clientX - this.robotPosition.x;
this.dragOffset.y = event.clientY - this.robotPosition.y;
this.startX = event.clientX;
this.startY = event.clientY;
},
onDrag(event) {
if (!this.isDragging) return;
const newX = event.clientX - this.dragOffset.x;
const newY = event.clientY - this.dragOffset.y;
//
const moveDistance = Math.sqrt(
Math.pow(event.clientX - this.startX, 2) +
Math.pow(event.clientY - this.startY, 2)
);
// 5
if (moveDistance > 5) {
this.hasMoved = true;
}
//
const maxX = window.innerWidth - 60;
const maxY = window.innerHeight - 60;
this.robotPosition.x = Math.max(0, Math.min(newX, maxX));
this.robotPosition.y = Math.max(0, Math.min(newY, maxY));
},
endDrag() {
this.isDragging = false;
this.hasMoved = false; //
},
handleResize() {
const maxX = window.innerWidth - 60;
const maxY = window.innerHeight - 60;
this.robotPosition.x = Math.min(this.robotPosition.x, maxX);
this.robotPosition.y = Math.min(this.robotPosition.y, maxY);
},
}, },
}; };
</script> </script>
@ -44,4 +157,80 @@ export default {
#app .theme-picker { #app .theme-picker {
display: none; display: none;
} }
.ai-robot-float {
position: fixed;
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
cursor: move;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
z-index: 9999;
user-select: none;
}
.ai-robot-float:hover {
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
.ai-robot-float img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
</style>
<style>
.ai-dialog {
border-radius: 12px;
overflow: visible !important;
border: 2px solid #0ac1c7;
box-shadow: 0 8px 30px rgba(10, 193, 199, 0.3);
}
.ai-dialog .el-dialog__header {
padding: 15px 20px;
border-bottom: 1px solid rgba(10, 193, 199, 0.2);
background: linear-gradient(135deg, #1a2a3a 0%, #2d3f52 100%);
}
.ai-dialog .el-dialog__title {
color: #0ac1c7;
font-size: 18px;
font-weight: 600;
}
.ai-dialog .el-dialog__body {
padding: 0;
height: 50vh;
overflow: hidden;
}
.ai-dialog__headerbtn {
font-size: 20px;
right: 20px;
top: 15px;
}
.ai-dialog__headerbtn .el-dialog__close {
color: #0ac1c7;
font-weight: bold;
}
.ai-dialog__headerbtn:hover .el-dialog__close {
color: #09a8ae;
}
.ai-dialog-content {
height: 100%;
display: flex;
flex-direction: column;
}
</style> </style>

208
src/api/ai.js

@ -0,0 +1,208 @@
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 - 超时时间毫秒默认3000030
* @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();
},
};
}

8
src/router/index.js

@ -96,6 +96,14 @@ export const constantRoutes = [
component: () => import("@/views/bigScreen/bigScreen"), component: () => import("@/views/bigScreen/bigScreen"),
meta: { title: "大屏总览", icon: "screen" }, meta: { title: "大屏总览", icon: "screen" },
}, },
// AI助手
{
path: "/ai",
name: "AI",
hidden: true,
component: () => import("@/views/ai/index"),
meta: { title: "AI助手", icon: "robot" },
},
]; ];
// 动态路由,基于用户权限动态去加载 // 动态路由,基于用户权限动态去加载

472
src/views/ai/index.vue

@ -0,0 +1,472 @@
<template>
<div class="chat-container">
<!-- 消息列表 -->
<div class="message-list" ref="messageList">
<div
v-for="(msg, index) in messages"
:key="index"
class="message-wrapper"
:class="{ 'user-message': msg.role === 'user', 'assistant-message': msg.role === 'assistant' }"
>
<!-- 头像可自定义图标 -->
<div class="avatar">
<img :src="logo" alt="AI" @error="handleImageError" />
</div>
<!-- 消息内容 -->
<div class="bubble">
<div class="message-content">
<!-- 如果是JSON格式用代码块显示 -->
<pre v-if="isJsonContent(msg.content)" class="json-content"><code>{{ formatJson(msg.content) }}</code></pre>
<!-- 否则用普通文本显示 -->
<div v-else v-html="formattedContent(msg)"></div>
</div>
<!-- 如果是AI消息且包含下载链接显示下载按钮 -->
<div v-if="msg.role === 'assistant' && msg.downloadUrl" class="download-area">
<a @click.prevent="handleDownload(msg.downloadUrl, index)" href="#" class="download-btn">
{{ msg.downloading ? '下载中...' : '📥 下载报表' }}
</a>
</div>
</div>
</div>
</div>
<!-- 底部输入区 -->
<div class="input-area">
<textarea
v-model="userInput"
@keydown.enter.prevent="sendMessage"
placeholder="输入你的需求,例如:生成昨天机房的能效报表,PDF格式..."
rows="1"
></textarea>
<button @click="sendMessage" :disabled="!userInput.trim() || loading">发送</button>
</div>
</div>
</template>
<script>
import logo from "@/assets/logo/logo.png";
import { createReportTask, createSSEConnection, downloadReport } from "@/api/ai";
export default {
name: 'ChatReport',
data() {
return {
userInput: '',
messages: [
{
role: 'assistant',
content: '您好!我是AI助手,可以帮助您生成机房能效报表。请告诉我您需要什么帮助?',
downloadUrl: ''
}
], // { role: 'user'/'assistant', content: '', downloadUrl: '' }
loading: false,
currentEventSource: null, // SSE
logo: logo,
};
},
mounted() {
this.$nextTick(() => {
this.scrollToBottom();
});
},
computed: {
// <br>
formattedContent() {
return (msg) => {
if (!msg.content) return '';
return msg.content.replace(/\n/g, '<br>');
};
},
// JSON
isJsonContent() {
return (content) => {
if (!content) return false;
content = content.trim();
return content.startsWith('{') && content.endsWith('}');
};
},
// JSON
formatJson() {
return (content) => {
try {
const parsed = JSON.parse(content);
return JSON.stringify(parsed, null, 2);
} catch (e) {
return content;
}
};
},
},
methods: {
//
sendMessage() {
const text = this.userInput.trim();
if (!text || this.loading) return;
// SSE
if (this.currentEventSource) {
this.currentEventSource.close();
this.currentEventSource = null;
}
//
this.messages.push({ role: 'user', content: text });
// AI
const aiMsgIndex = this.messages.length;
this.messages.push({ role: 'assistant', content: '', downloadUrl: '' });
this.scrollToBottom();
//
this.userInput = '';
this.loading = true;
//
createReportTask(text)
.then(data => {
const taskId = data.taskId;
// SSE
this.connectSSE(taskId, aiMsgIndex);
})
.catch(err => {
this.handleError('创建任务失败:' + (err.message || err), aiMsgIndex);
});
},
//
closeDialog() {
this.$emit('close');
},
//
handleImageError() {
console.log('图片加载失败,使用默认样式');
},
//
async handleDownload(downloadUrl, msgIndex) {
try {
this.$set(this.messages[msgIndex], 'downloading', true);
const { blob, fileName } = await downloadReport(downloadUrl);
//
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
this.$set(this.messages[msgIndex], 'downloading', false);
} catch (error) {
console.error('Download failed:', error);
this.$message.error('下载失败:' + (error.message || error));
this.$set(this.messages[msgIndex], 'downloading', false);
}
},
// SSE
connectSSE(taskId, msgIndex) {
const eventSource = createSSEConnection(taskId, {
onReasoning: (data) => {
//
//
if (!this.messages[msgIndex].fullContent) {
this.$set(this.messages[msgIndex], 'fullContent', data);
} else {
this.$set(this.messages[msgIndex], 'fullContent', this.messages[msgIndex].fullContent + data);
}
},
onCompleted: (downloadUrl) => {
//
const fullContent = this.messages[msgIndex].fullContent || '';
this.typewriterEffect(msgIndex, fullContent);
//
this.$set(this.messages[msgIndex], 'downloadUrl', downloadUrl);
this.loading = false;
eventSource.close();
this.currentEventSource = null;
},
onFailed: (errorMsg) => {
this.handleError('生成失败:' + errorMsg, msgIndex);
eventSource.close();
this.currentEventSource = null;
},
onError: (error) => {
console.error('SSE error', error);
//
if (this.loading) {
this.handleError('连接中断,请重试', msgIndex);
}
eventSource.close();
this.currentEventSource = null;
},
});
this.currentEventSource = eventSource;
},
//
typewriterEffect(msgIndex, text, index = 0) {
if (index >= text.length) {
return;
}
this.$set(this.messages[msgIndex], 'content', text.substring(0, index + 1));
this.scrollToBottom();
// 1ms
setTimeout(() => {
this.typewriterEffect(msgIndex, text, index + 1);
}, 1);
},
//
handleError(errorText, msgIndex) {
// AI
this.$set(this.messages[msgIndex], 'content', errorText);
this.loading = false;
if (this.currentEventSource) {
this.currentEventSource.close();
this.currentEventSource = null;
}
this.scrollToBottom();
},
//
scrollToBottom() {
this.$nextTick(() => {
const container = this.$refs.messageList;
if (container) {
container.scrollTop = container.scrollHeight;
}
});
},
},
beforeDestroy() {
// SSE
if (this.currentEventSource) {
this.currentEventSource.close();
this.currentEventSource = null;
}
},
};
</script>
<style scoped>
.chat-container {
display: flex;
flex-direction: column;
height: 100%;
min-height: 400px;
background: linear-gradient(135deg, #1a2a3a 0%, #2d3f52 100%);
position: relative;
}
.message-list {
flex: 1;
overflow-y: auto;
padding: 15px;
min-height: 0;
scrollbar-width: thin;
scrollbar-color: #4a6fa5 #1a2a3a;
}
.message-list::-webkit-scrollbar {
width: 6px;
}
.message-list::-webkit-scrollbar-track {
background: #1a2a3a;
}
.message-list::-webkit-scrollbar-thumb {
background: #4a6fa5;
border-radius: 3px;
}
.message-wrapper {
display: flex;
margin-bottom: 15px;
}
.user-message {
flex-direction: row-reverse;
}
.avatar {
width: 44px;
height: 44px;
margin: 0 10px;
flex-shrink: 0;
min-width: 44px;
display: flex;
align-items: center;
justify-content: center;
}
.avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: contain;
background: linear-gradient(135deg, #0ac1c7 0%, #09a8ae 100%);
border: 2px solid #0ac1c7;
box-shadow: 0 0 15px rgba(10, 193, 199, 0.6), 0 0 30px rgba(10, 193, 199, 0.3);
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
padding: 8px;
box-sizing: border-box;
}
/* 用户头像样式 */
.user-message .avatar img {
background: linear-gradient(135deg, #4a6fa5 0%, #3d5a80 100%);
border-color: #4a6fa5;
box-shadow: 0 0 15px rgba(74, 111, 165, 0.6), 0 0 30px rgba(74, 111, 165, 0.3);
}
.bubble {
max-width: 70%;
padding: 12px 16px;
border-radius: 16px;
background: rgba(45, 63, 82, 0.8);
border: 1px solid rgba(10, 193, 199, 0.3);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
word-wrap: break-word;
font-size: 14px;
backdrop-filter: blur(10px);
}
.user-message .bubble {
background: linear-gradient(135deg, #0ac1c7 0%, #09a8ae 100%);
border-color: #0ac1c7;
}
.message-content {
line-height: 1.6;
color: #e0e6ed;
word-wrap: break-word;
}
.user-message .message-content {
color: #ffffff;
}
/* JSON内容样式 */
.json-content {
background: rgba(0, 0, 0, 0.4);
border-radius: 8px;
padding: 12px;
margin: 0;
overflow-x: auto;
font-family: 'Courier New', Courier, monospace;
font-size: 13px;
line-height: 1.5;
color: #e0e6ed;
white-space: pre-wrap;
word-wrap: break-word;
border: 1px solid rgba(10, 193, 199, 0.2);
}
.json-content code {
color: #e0e6ed;
}
.download-area {
margin-top: 10px;
text-align: center;
}
.download-btn {
display: inline-block;
padding: 8px 20px;
background: linear-gradient(135deg, #0ac1c7 0%, #09a8ae 100%);
color: white;
text-decoration: none;
border-radius: 20px;
font-size: 13px;
transition: all 0.3s ease;
border: 1px solid #0ac1c7;
box-shadow: 0 2px 8px rgba(10, 193, 199, 0.3);
}
.download-btn:hover {
background: linear-gradient(135deg, #09a8ae 0%, #088a90 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(10, 193, 199, 0.5);
}
.input-area {
display: flex;
padding: 15px;
background: rgba(26, 42, 58, 0.95);
border-top: 1px solid rgba(10, 193, 199, 0.2);
flex-shrink: 0;
height: auto;
backdrop-filter: blur(10px);
}
.input-area textarea {
flex: 1;
padding: 12px 16px;
border: 1px solid rgba(10, 193, 199, 0.3);
border-radius: 24px;
resize: none;
outline: none;
font-size: 14px;
line-height: 1.4;
max-height: 80px;
min-height: 44px;
background: rgba(45, 63, 82, 0.6);
color: #e0e6ed;
transition: all 0.3s ease;
}
.input-area textarea::placeholder {
color: #6b8a9e;
}
.input-area textarea:focus {
border-color: #0ac1c7;
box-shadow: 0 0 10px rgba(10, 193, 199, 0.3);
background: rgba(45, 63, 82, 0.8);
}
.input-area button {
margin-left: 12px;
padding: 0 24px;
background: linear-gradient(135deg, #0ac1c7 0%, #09a8ae 100%);
color: white;
border: none;
border-radius: 24px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
height: 44px;
align-self: center;
font-weight: 500;
box-shadow: 0 2px 8px rgba(10, 193, 199, 0.3);
}
.input-area button:hover:not(:disabled) {
background: linear-gradient(135deg, #09a8ae 0%, #088a90 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(10, 193, 199, 0.5);
}
.input-area button:disabled {
background: #4a6fa5;
cursor: not-allowed;
opacity: 0.6;
box-shadow: none;
}
</style>

686
src/views/index.vue

@ -62,161 +62,90 @@
</div> </div>
</div> </div>
<div class="project-data"> <div class="project-data">
<div class="special-div" style="width: 49.4%"> <div class="special-div" style="width: 60%">
<div class="special-top"> <div class="special-top">
<div class="special-title">出水温度</div> <div class="special-title">项目概况</div>
</div> </div>
<div class="overview"> <div class="overview">
<div class="overview-li" @click="goEnergy"> <div class="overview-li" @click="goEnergy">
<div>离心机高温出水温度</div> <div>总耗电量(kwh)</div>
<div <div class="overview-details">{{ projectView.totalEle }}</div>
class="overview-details"
v-html="getTempDisplay('离心机高温出水温度', outWaterTemperature)"
></div>
</div> </div>
<div class="overview-li" @click="goEnergy"> <div class="overview-li" @click="goEnergy">
<div>中温换热出水温度</div> <div>总热水补水()</div>
<div <div class="overview-details">
class="overview-details" {{ projectView.totalWater }}
v-html="getTempDisplay('中温换热出水温度', outWaterTemperature)" </div>
></div>
</div> </div>
<div class="overview-li" @click="goEnergy"> <div class="overview-li" @click="goEnergy">
<div>低温1换热出水温度</div> <div>总蒸汽流量()</div>
<div <div class="overview-details">
class="overview-details" {{ projectView.totalGas }}
v-html="getTempDisplay('低温1换热出水温度', outWaterTemperature)" </div>
></div>
</div> </div>
<div class="overview-li" @click="goEnergy"> <div class="overview-li" @click="goEnergy">
<div>低温2换热出水温度</div> <div>总产冷量(kw)</div>
<div <div class="overview-details">
class="overview-details" {{ projectView.totalCold }}
v-html="getTempDisplay('低温2换热出水温度', outWaterTemperature)" </div>
></div>
</div> </div>
</div>
</div>
<div class="special-div" style="width: 49.4%">
<div class="special-top">
<div class="special-title">热量数据</div>
</div>
<div class="overview">
<div class="overview-li" @click="goEnergy"> <div class="overview-li" @click="goEnergy">
<div>生产积累热量</div> <div>今年耗电量(kwh)</div>
<div class="overview-details"> <div class="overview-details">
{{ heatData.productionHeatSum }}kwh {{ projectView.yearEle }}
</div> </div>
</div> </div>
<div class="overview-li" @click="goEnergy"> <div class="overview-li" @click="goEnergy">
<div>散热累计热量</div> <div>今年热水补水()</div>
<div class="overview-details"> <div class="overview-details">
{{ heatData.dissipationHeatSum }}kwh {{ projectView.yearWater }}
</div> </div>
</div> </div>
<div class="overview-li" @click="goEnergy"> <div class="overview-li" @click="goEnergy">
<div>总热量回收</div> <div>今年蒸汽流量()</div>
<div class="overview-details"> <div class="overview-details">
{{ heatData.totalHeatRecoverySum }}kwh {{ projectView.yearGas }}
</div> </div>
</div> </div>
<div class="overview-li" @click="goEnergy"> <div class="overview-li" @click="goEnergy">
<div>热利用率</div> <div>今年产冷量(kw)</div>
<div class="overview-details">{{ heatData.heatUtilization }}%</div> <div class="overview-details">
{{ projectView.yearCold }}
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="special-div" style="width: 39%">
<div class="special-top">
<div class="special-title">冷源系统</div>
</div>
<view-energy></view-energy>
</div>
</div> </div>
<div class="project-bie"> <div class="project-bie">
<div class="special-div" style="width: 49.4%"> <div class="special-div">
<div class="special-top"> <div class="special-top">
<div class="special-title">系统数据</div> <div class="special-title">冷源能耗</div>
</div>
<div class="hot-tem">
<div class="tem-li">
<div class="tem-title">离心机入口温度</div>
<div
class="tem-detail"
v-html="getTempDisplay('离心机入口温度', systemData)"
></div>
</div>
<div class="tem-li">
<div class="tem-title">离心机出水温度</div>
<div
class="tem-detail"
v-html="getTempDisplay('离心机出水温度', systemData)"
></div>
</div>
<div class="tem-li">
<div class="tem-title">保障进水温度</div>
<div
class="tem-detail"
v-html="getTempDisplay('保障进水温度', systemData)"
></div>
</div>
</div> </div>
<div class="pressure"> <view-cold-sys :subData="coldSys"></view-cold-sys>
<view-cold-sys </div>
:subData="getDeviceData('离心机进水压力')" <div class="special-div">
:title="'离心机进水压力'" <div class="special-top">
></view-cold-sys> <div class="special-title">热水系统</div>
<view-cold-sys
:subData="getDeviceData('离心机出水压力')"
:title="'离心机出水压力'"
></view-cold-sys>
<view-cold-sys
:subData="getDeviceData('离心机压差')"
:title="'离心机压差'"
></view-cold-sys>
</div> </div>
<hot-water :subData="hotWaterSys"></hot-water>
</div> </div>
<div class="bir-right"> <div class="special-div">
<div class="special-div"> <div class="special-top">
<div class="special-top"> <div class="special-title">风柜系统</div>
<div class="special-title">阀门开度</div>
</div>
<hot-water :subData="valveData"></hot-water>
</div> </div>
<div class="later-data"> <airc-and-windc-meter :subData="airAndWindSys"></airc-and-windc-meter>
<div class="special-div"> </div>
<div class="special-top"> <div class="special-div">
<div class="special-title">热回收数据</div> <div class="special-top">
</div> <div class="special-title">温度系统</div>
<div class="hot-data">
<div class="hot-li">
<div class="hot-title">瞬时热量:</div>
<div class="hot-detail">{{heatRecoveryData.instantaneousHeatSum}}kw</div>
</div>
<div class="hot-li">
<div class="hot-title">日累计热量:</div>
<div class="hot-detail">{{heatRecoveryData.dailyAccumulatedHeat}}kwh</div>
</div>
<div class="hot-li">
<div class="hot-title">累计热量:</div>
<div class="hot-detail">{{heatRecoveryData.accumulatedHeatSum}}kwh</div>
</div>
</div>
</div>
<div class="special-div">
<div class="special-top">
<div class="special-title">应用测数据</div>
</div>
<div class="hot-data">
<div class="hot-li">
<div class="hot-title">瞬时热量:</div>
<div class="hot-detail">{{applicationData.instantaneousHeatSum}}kw</div>
</div>
<div class="hot-li">
<div class="hot-title">日累计热量:</div>
<div class="hot-detail">{{applicationData.dailyAccumulatedHeat}}kwh</div>
</div>
<div class="hot-li">
<div class="hot-title">累计热量:</div>
<div class="hot-detail">{{applicationData.accumulatedHeatSum}}kwh</div>
</div>
</div>
</div>
</div> </div>
<tem-meter :subData="temSys"></tem-meter>
</div> </div>
</div> </div>
<el-dialog <el-dialog
@ -253,15 +182,27 @@
</template> </template>
<script> <script>
import * as echarts from "echarts";
import { imgUrl } from "@/utils/global"; import { imgUrl } from "@/utils/global";
import { introduction, changeLogo, ersDatas } from "@/api/index"; import {
introduction,
changeLogo,
viewProfile,
viewMainParams,
} from "@/api/index";
import viewEnergy from "./components/viewEnergy.vue";
import ViewColdSys from "./components/viewColdSys.vue"; import ViewColdSys from "./components/viewColdSys.vue";
import HotWater from "./components/hotWater.vue"; import HotWater from "./components/hotWater.vue";
import AircAndWindcMeter from "./components/aircAndWindcMeter.vue";
import TemMeter from "./components/temMeter.vue";
export default { export default {
components: { components: {
viewEnergy,
ViewColdSys, ViewColdSys,
HotWater, HotWater,
AircAndWindcMeter,
TemMeter,
}, },
data() { data() {
return { return {
@ -273,22 +214,51 @@ export default {
buildingArea: "", buildingArea: "",
operateStartTime: "", operateStartTime: "",
}, },
heatData: {}, // projectView: {
systemData: [], // totalEle: "",
valveData: [], // totalWater: "",
outWaterTemperature: [], // totalGas: "",
applicationData: {}, // totalCold: "",
heatRecoveryData: {}, // yearEle: "",
coldSys: [], yearWater: "",
hotWaterSys: [], yearGas: "",
yearCold: "",
},
dialogVisible: false, dialogVisible: false,
selectedFile: null, selectedFile: null,
viewImageUrl: "", viewImageUrl: "",
chartInstance: null,
data1: [],
data2: [],
xTable: [],
chartsData1: null,
chartsData2: null,
isShowHome: true,
//
coldSys: [],
hotWaterSys: [],
airAndWindSys: [],
temSys: [],
}; };
}, },
mounted() { mounted() {
this.getProject(); this.getProject();
this.getErsData(); this.getHomeData();
this.getSubData();
// //
// setTimeout(() => {
// this.requestFullscreen();
// }, 100); // 100
// this.initChart();
// window.addEventListener("resize", this.screenAdapter);
// this.screenAdapter();
// this.getChartsData();
},
destroyed() {
//mounted
// window.removeEventListener("resize", this.screenAdapter);
}, },
methods: { methods: {
// //
@ -403,91 +373,270 @@ export default {
} }
}); });
}, },
// //
getErsData() { getHomeData() {
let data = { viewProfile().then((res) => {
systemType: "7",
};
ersDatas(data).then((res) => {
if (res.code == 200) { if (res.code == 200) {
console.log("首页返回数据----------", res.rows[0]); this.projectView = res.rows[0];
this.heatData = res.rows[0].heatData;
this.systemData = res.rows[0].systemData;
this.valveData = res.rows[0].valveData;
this.outWaterTemperature = res.rows[0].outWaterTemperature;
this.applicationData = res.rows[0].applicationData;
this.heatRecoveryData = res.rows[0].heatRecoveryData;
//
this.addCentrifugePressureDiff();
console.log("deviceName-1", this.systemData);
} }
}); });
}, },
// systemData goEnergy(){
addCentrifugePressureDiff() { this.$router.push("/comprehensiveEnergy/systemEnergy")
//
const inletPressure = this.systemData.find(
(item) => item.deviceTypeName === "离心机进水压力"
);
const outletPressure = this.systemData.find(
(item) => item.deviceTypeName === "离心机出水压力"
);
//
if (inletPressure && outletPressure) {
// -
const pressureDiff =
parseFloat(outletPressure.curValue) -
parseFloat(inletPressure.curValue);
// 11
const status =
inletPressure.status === 1 || outletPressure.status === 1 ? 1 : 0;
//
const pressureDiffObj = {
paramType: "14", //
curTime: null,
orderNum: this.systemData.length + 1, // +1
deviceTypeName: "离心机压差",
otherName: "压差计算值",
deviceName: "离心机压差",
curValue: parseFloat(pressureDiff.toFixed(2)), //
status: status,
};
// systemData
this.systemData.push(pressureDiffObj);
console.log("添加离心机压差对象:", pressureDiffObj);
} else {
console.warn("找不到进水压力或出水压力数据");
}
}, },
// //chartInstance
getTempDisplay(deviceName, data) { initChart() {
// console.log("this.systemData",this.systemData) this.chartInstance = echarts.init(this.$refs.charts_ref);
// console.log("deviceName",deviceName) this.option = {
const item = data.find((item) => item.deviceTypeName === deviceName); tooltip: {
// console.log("item", item); trigger: "axis",
},
if (!item) return "--℃"; grid: {
left: "3%",
if (item.status === 1) { right: "4%",
return '<span style="color: red">异常</span>'; bottom: "3%",
} containLabel: true,
return `${item.curValue}`; },
legend: {
data: ["消费总金额(元)", "消费总次数(次)"],
icon: "cricle", //
// //
textStyle: {
color: "#ffff",
fontSize: 12, //
},
left: "center",
top: "10",
//
itemGap: 5,
itemWidth: 10,
itemHeight: 5,
},
xAxis: {
type: "category",
//true
boundaryGap: true,
// data: [
// "1",
// "2",
// "3",
// "4",
// "5",
// "6",
// "7",
// "8",
// "9",
// "10",
// "11",
// "12",
// "13",
// "14",
// "15",
// "16",
// "17",
// "18",
// "19",
// "20",
// "21",
// "22",
// "23",
// "24",
// ],
data: this.xTable,
// x
axisLabel: {
// interval: 0, //x
// rotate: 30, //x30
color: "rgba(255, 255, 255, 1)",
},
axisTick: {
show: false, // 线
},
// x
axisLine: {
show: true,
lineStyle: {
// X
color: "#365576",
},
},
splitLine: {
lineStyle: {
color: "#e2e6f0",
},
}, //x线
},
yAxis: {
miniInterval: 5,
type: "value",
// name: "",
// name
nameTextStyle: {
color: "rgba(255, 255, 255, 1)",
fontSize: 12,
},
// y
axisLabel: {
color: "rgba(255, 255, 255, 1)",
},
// y
axisLine: {
show: true,
lineStyle: {
color: "#365576", // y 线
},
},
//y线
// splitNumber: 10,
// y线
splitLine: {
lineStyle: {
color: "#1a3d62", // 线
type: "dashed", // 线线
},
},
},
series: [
{
name: "消费总金额(元)",
type: "line",
// data: [
// 20, 42, 12, 43, 22, 56, 22, 7, 88, 34, 62, 12, 23, 41, 23, 22, 87,
// 56, 54, 34, 33, 10, 32, 54, 39, 27,
// ],
data: this.data1,
itemStyle: {
color: " #08c8ff",
},
},
{
name: "消费总次数(次)",
type: "line",
// data: [
// 40, 42, 2, 45, 32, 16, 92, 7, 88, 34, 62, 12, 23, 41, 23, 22, 43,
// 56, 524, 32, 3, 11, 32, 54, 39, 27,
// ],
data: this.data2,
itemStyle: {
color: "#277dff",
},
},
],
};
//
this.chartInstance.setOption(this.option, true);
},
// + 线
screenAdapter() {
//,2.6 mes_ref
const titleFontSize = (this.$refs.charts_ref.offsetWidth / 100) * 0.8;
//optionoption
const adapterOption = {};
//.chartInstanceoptiondataoption
this.chartInstance.setOption(adapterOption);
//resize
this.chartInstance.resize();
}, },
// - //
getDeviceData(deviceTypeName) { getChartsData() {
return ( const now = new Date();
this.systemData.find( const startDate = new Date(
(item) => item.deviceTypeName === deviceTypeName Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - 30)
) || {} );
const endDate = new Date(
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - 1)
); );
const startDateStr = startDate.toISOString().slice(0, 10);
const endDateStr = endDate.toISOString().slice(0, 10);
let params = {
page: 0,
limit: 10,
startDate: startDateStr,
endDate: endDateStr,
type: "projectDay",
buildingName: "",
};
// console.log("30", params)
findTableData(params).then((res) => {
// console.log("30", res)
if (res) {
(this.data1 = []),
(this.data2 = []),
(this.xTable = []),
res.data.forEach((item) => {
this.data1.push(item.AmountConsu);
this.data2.push(item.PersonTime);
this.xTable.push(item.DATE);
});
// console.log("xTable------------", this.xTable);
// console.log("data1--------------", this.data1);
// console.log("data2--------------", this.data2);
const adapterOption = {
xAxis: {
data: this.xTable,
},
series: [
{
data: this.data1,
},
{
data: this.data2,
},
],
};
//.chartInstanceoptiondataoption
this.chartInstance.setOption(adapterOption);
//resize
this.chartInstance.resize();
} else {
const adapterOption = {
xAxis: {
data: [],
},
series: [
{
data: [],
},
{
data: [],
},
],
};
//.chartInstanceoptiondataoption
this.chartInstance.setOption(adapterOption);
//resize
this.chartInstance.resize();
}
});
}, },
goEnergy() { //
// this.$router.push("/comprehensiveEnergy/systemEnergy"); getSubData() {
viewMainParams().then((res) => {
console.log("系统参数返回", res);
console.log("冷源监控返回", res.rows[0]);
this.coldSys = [];
this.hotWaterSys = [];
this.airAndWindSys = [];
this.temSys = [];
if (res.code == 200 && res.rows) {
res.rows.forEach((val) => {
if (val.name.includes("冷源")) {
this.coldSys = val.values;
console.log("this.coldSys111111111111", this.coldSys);
}
if (val.name.includes("热水")) {
this.hotWaterSys = val.values;
}
if (val.name.includes("风柜")) {
this.airAndWindSys = val.values;
}
if (val.name.includes("温度")) {
this.temSys = val.values;
}
});
}
});
}, },
}, },
}; };
@ -555,40 +704,32 @@ export default {
margin: 16px 0; margin: 16px 0;
.overview { .overview {
width: 100%; width: 100%;
padding: 30px 20px 5px 20px; padding-top: 0.32rem;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: space-between;
flex-wrap: wrap; flex-wrap: wrap;
.overview-li { .overview-li {
cursor: pointer; cursor: pointer;
width: 49%; width: calc(25%);
height: 1.65rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: space-between; justify-content: center;
background-image: url(../assets/images/overview-img.png);
background-repeat: no-repeat;
background-size: 1.65rem 1.65rem;
background-position: center center; background-position: center center;
margin-bottom: 15px; margin-bottom: 0.3rem;
font-family: SourceHanSansCN-Regular; font-family: SourceHanSansCN-Regular;
font-size: 15px; font-size: 0.15rem;
color: #ffffff; color: #ffffff;
/* 从 rgba(41, 128, 185, 0.8) 渐变到透明 */
background: linear-gradient(
to bottom,
rgba(31, 100, 146, 0.6) 0%,
rgba(33, 65, 87, 0.3) 50%,
rgba(40, 62, 77, 0) 100%
);
border-radius: 8px;
padding: 5px;
color: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
.overview-details { .overview-details {
font-family: DIN-Bold; font-family: DIN-Bold;
font-size: 20px; font-size: 0.2rem;
color: #15e1fd; color: #15e1fd;
margin-top: 5px; margin-top: 0.05rem;
} }
} }
} }
@ -603,90 +744,29 @@ export default {
flex-direction: row; flex-direction: row;
align-items: stretch; align-items: stretch;
justify-content: space-between; justify-content: space-between;
.pressure { .special-div {
width: 100%; width: 24.5%;
display: flex;
flex-direction: row;
align-items: center;
margin-top: 25px;
} }
.hot-tem { }
width: 100%; @media (max-width: 1485px) {
padding: 0 20px; .overview-li {
display: flex; width: calc(33.33%) !important;
flex-direction: row; height: 2.5rem !important;
justify-content: space-between; background-size: 2.5rem 2.5rem !important;
align-items: stretch; margin-bottom: 0.3rem;
flex-wrap: wrap; font-size: 0.22rem !important;
margin-top: 25px; .overview-details {
.tem-li { font-size: 0.27rem !important;
background-color: rgba(93, 125, 143, 0.5);
width: 32%;
padding: 10px;
border-radius: 10px;
display: flex;
flex-direction: column;
justify-content: space-between;
margin-bottom: 20px;
.tem-title {
font-size: 15px;
font-weight: 600;
color: #ffffff;
}
.tem-detail {
margin-top: 8px;
font-size: 20px;
font-weight: 600;
color: #f89615;
text-align: center;
}
} }
} }
.bir-right { .project-bie {
width: 49.4%; flex-wrap: wrap !important;
display: flex;
flex-direction: column;
justify-content: space-between;
.special-div { .special-div {
width: 100%; width: 49.5% !important;
margin-bottom: 20px; margin-bottom: 0.25rem;
}
.later-data {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
.special-div {
width: 49%;
margin-bottom: 0;
}
.hot-data {
width: 100%;
padding: 25px 25px 5px 25px;
.hot-li {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
.hot-title {
font-size: 15px;
font-weight: 600;
color: #ffffff;
}
.hot-detail {
font-size: 20px;
font-weight: 600;
color: #f5f127;
}
}
}
} }
} }
} }
@media (max-width: 1485px) {
}
// 2000px // 2000px
@media (min-width: 2000px) { @media (min-width: 2000px) {
} }

693
src/views/index_ers.vue

@ -0,0 +1,693 @@
<template>
<div class="app-container">
<div class="special-div">
<div class="special-top">
<div class="special-title">项目简介</div>
</div>
<div class="project-all">
<img
class="project-img"
:src="imgUrl + projectObj.logo"
@click="showDialog"
alt="Base64 Image"
/>
<div class="project-li">
<div class="list-con">
<div class="project-left">
<img
class="left-icon"
src="../assets/images/project-icon1.png"
alt=""
/>
<div class="project-name">项目名称</div>
</div>
<div class="project-right">{{ projectObj.proName }}</div>
</div>
<div class="list-con">
<div class="project-left">
<img
class="left-icon"
src="../assets/images/project-icon2.png"
alt=""
/>
<div class="project-name">建筑面积</div>
</div>
<div class="project-right">{{ projectObj.buildingArea }}</div>
</div>
<div class="list-con">
<div class="project-left">
<img
class="left-icon"
src="../assets/images/project-icon3.png"
alt=""
/>
<div class="project-name">运营地址</div>
</div>
<div class="project-right">{{ projectObj.proAddr }}</div>
</div>
</div>
<div class="project-li">
<div class="list-con">
<div class="project-left">
<img
class="left-icon"
src="../assets/images/project-icon3.png"
alt=""
/>
<div class="project-name">项目运行开始时间</div>
</div>
<div class="project-right">{{ projectObj.operateStartTime }}</div>
</div>
</div>
</div>
</div>
<div class="project-data">
<div class="special-div" style="width: 49.4%">
<div class="special-top">
<div class="special-title">出水温度</div>
</div>
<div class="overview">
<div class="overview-li" @click="goEnergy">
<div>离心机高温出水温度</div>
<div
class="overview-details"
v-html="getTempDisplay('离心机高温出水温度', outWaterTemperature)"
></div>
</div>
<div class="overview-li" @click="goEnergy">
<div>中温换热出水温度</div>
<div
class="overview-details"
v-html="getTempDisplay('中温换热出水温度', outWaterTemperature)"
></div>
</div>
<div class="overview-li" @click="goEnergy">
<div>低温1换热出水温度</div>
<div
class="overview-details"
v-html="getTempDisplay('低温1换热出水温度', outWaterTemperature)"
></div>
</div>
<div class="overview-li" @click="goEnergy">
<div>低温2换热出水温度</div>
<div
class="overview-details"
v-html="getTempDisplay('低温2换热出水温度', outWaterTemperature)"
></div>
</div>
</div>
</div>
<div class="special-div" style="width: 49.4%">
<div class="special-top">
<div class="special-title">热量数据</div>
</div>
<div class="overview">
<div class="overview-li" @click="goEnergy">
<div>生产积累热量</div>
<div class="overview-details">
{{ heatData.productionHeatSum }}kwh
</div>
</div>
<div class="overview-li" @click="goEnergy">
<div>散热累计热量</div>
<div class="overview-details">
{{ heatData.dissipationHeatSum }}kwh
</div>
</div>
<div class="overview-li" @click="goEnergy">
<div>总热量回收</div>
<div class="overview-details">
{{ heatData.totalHeatRecoverySum }}kwh
</div>
</div>
<div class="overview-li" @click="goEnergy">
<div>热利用率</div>
<div class="overview-details">{{ heatData.heatUtilization }}%</div>
</div>
</div>
</div>
</div>
<div class="project-bie">
<div class="special-div" style="width: 49.4%">
<div class="special-top">
<div class="special-title">系统数据</div>
</div>
<div class="hot-tem">
<div class="tem-li">
<div class="tem-title">离心机入口温度</div>
<div
class="tem-detail"
v-html="getTempDisplay('离心机入口温度', systemData)"
></div>
</div>
<div class="tem-li">
<div class="tem-title">离心机出水温度</div>
<div
class="tem-detail"
v-html="getTempDisplay('离心机出水温度', systemData)"
></div>
</div>
<div class="tem-li">
<div class="tem-title">保障进水温度</div>
<div
class="tem-detail"
v-html="getTempDisplay('保障进水温度', systemData)"
></div>
</div>
</div>
<!-- <div class="pressure">
<view-cold-sys
:subData="getDeviceData('离心机进水压力')"
:title="'离心机进水压力'"
></view-cold-sys>
<view-cold-sys
:subData="getDeviceData('离心机出水压力')"
:title="'离心机出水压力'"
></view-cold-sys>
<view-cold-sys
:subData="getDeviceData('离心机压差')"
:title="'离心机压差'"
></view-cold-sys>
</div> -->
</div>
<div class="bir-right">
<div class="special-div">
<div class="special-top">
<div class="special-title">阀门开度</div>
</div>
<hot-water :subData="valveData"></hot-water>
</div>
<div class="later-data">
<div class="special-div">
<div class="special-top">
<div class="special-title">热回收数据</div>
</div>
<div class="hot-data">
<div class="hot-li">
<div class="hot-title">瞬时热量:</div>
<div class="hot-detail">{{heatRecoveryData.instantaneousHeatSum}}kw</div>
</div>
<div class="hot-li">
<div class="hot-title">日累计热量:</div>
<div class="hot-detail">{{heatRecoveryData.dailyAccumulatedHeat}}kwh</div>
</div>
<div class="hot-li">
<div class="hot-title">累计热量:</div>
<div class="hot-detail">{{heatRecoveryData.accumulatedHeatSum}}kwh</div>
</div>
</div>
</div>
<div class="special-div">
<div class="special-top">
<div class="special-title">应用测数据</div>
</div>
<div class="hot-data">
<div class="hot-li">
<div class="hot-title">瞬时热量:</div>
<div class="hot-detail">{{applicationData.instantaneousHeatSum}}kw</div>
</div>
<div class="hot-li">
<div class="hot-title">日累计热量:</div>
<div class="hot-detail">{{applicationData.dailyAccumulatedHeat}}kwh</div>
</div>
<div class="hot-li">
<div class="hot-title">累计热量:</div>
<div class="hot-detail">{{applicationData.accumulatedHeatSum}}kwh</div>
</div>
</div>
</div>
</div>
</div>
</div>
<el-dialog
:visible.sync="dialogVisible"
title="上传图片"
width="500px"
@close="addExpenseClose"
>
<!-- 使用 el-upload 组件 -->
<el-upload
class="upload-demo"
ref="uploadComponent"
action="#"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:before-upload="beforeUpload"
:limit="1"
:on-exceed="handleExceed"
:on-change="handleFileChange"
accept="image/png, image/jpeg"
:http-request="customHttpRequest"
>
<i class="el-icon-plus"></i>
</el-upload>
<div slot="footer" class="dialog-footer">
<!-- 取消按钮 -->
<el-button @click="dialogVisible = false">取消</el-button>
<!-- 确定按钮 -->
<el-button type="primary" @click="uploadFile">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { imgUrl } from "@/utils/global";
import { introduction, changeLogo, ersDatas } from "@/api/index";
// import ViewColdSys from "./components/viewColdSys.vue";
import HotWater from "./components/hotWater.vue";
export default {
components: {
// ViewColdSys,
HotWater,
},
data() {
return {
imgUrl: "",
projectObj: {
proName: "",
logo: "",
proAddr: "",
buildingArea: "",
operateStartTime: "",
},
heatData: {}, //
systemData: [], //
valveData: [], //
outWaterTemperature: [], //
applicationData: {}, //
heatRecoveryData: {}, //
coldSys: [],
hotWaterSys: [],
dialogVisible: false,
selectedFile: null,
viewImageUrl: "",
};
},
mounted() {
this.getProject();
this.getErsData();
},
methods: {
//
requestFullscreen() {
const element = document.documentElement;
console.log("全屏了吗");
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
// Firefox
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
// Chrome, Safari and Opera
element.webkitRequestFullscreen();
} else if (element.msRequestFullscreen) {
// IE/Edge
element.msRequestFullscreen();
}
},
//
getProject() {
this.imgUrl = imgUrl;
introduction().then((res) => {
console.log("项目资料", res);
if (res.code == 200) {
this.projectObj = res.rows[0];
}
});
},
//
showDialog() {
this.dialogVisible = true;
},
//
handleFileChange(file, fileList) {
this.selectedFile = file.raw;
},
//
addExpenseClose() {
//
this.$refs.uploadComponent.clearFiles();
this.viewImageUrl = "";
},
//
reset() {
this.dialogData = {};
//
this.$refs.uploadComponent.clearFiles();
this.viewImageUrl = "";
},
//
handleRemove(file, fileList) {
console.log(file, fileList);
this.selectedFile = {};
},
//
handlePictureCardPreview(file) {
this.dialogVisible = true;
this.viewImageUrl = file.url;
},
//
handleUploadSuccess(response, file, fileList) {},
//
handleFileChange(file, fileList) {
console.log("file", file);
this.selectedFile = file.raw;
},
processFile(file) {
// file.raw
console.log("处理的文件", file);
//
const reader = new FileReader();
reader.onload = (event) => {
console.log("文件内容", event.target.result);
};
reader.readAsDataURL(file);
},
customHttpRequest(options) {
//
const file = options.file;
},
beforeUpload(file) {
const isJpgOrPng =
file.type === "image/jpeg" || file.type === "image/png";
if (!isJpgOrPng) {
this.$message.error("上传图片只能是 JPG 或 PNG 格式!");
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
this.$message.error("上传图片大小不能超过 2MB!");
}
return isJpgOrPng && isLt2M;
},
handleExceed(files, fileList) {
this.$message.warning(`上传文件数量超过限制, 当前限制为 1 张`);
},
//
uploadFile() {
console.log("这里进行请求");
let data = {
proId: 0,
logo: this.selectedFile,
};
changeLogo(data).then((res) => {
if (res.code == 200) {
this.$modal.msgSuccess("上传成功");
this.getProject();
this.dialogVisible = false;
} else {
this.$message.error("上传失败");
this.dialogVisible = false;
}
});
},
//
getErsData() {
let data = {
systemType: "7",
};
ersDatas(data).then((res) => {
if (res.code == 200) {
console.log("首页返回数据----------", res.rows[0]);
this.heatData = res.rows[0].heatData;
this.systemData = res.rows[0].systemData;
this.valveData = res.rows[0].valveData;
this.outWaterTemperature = res.rows[0].outWaterTemperature;
this.applicationData = res.rows[0].applicationData;
this.heatRecoveryData = res.rows[0].heatRecoveryData;
//
this.addCentrifugePressureDiff();
console.log("deviceName-1", this.systemData);
}
});
},
// systemData
addCentrifugePressureDiff() {
//
const inletPressure = this.systemData.find(
(item) => item.deviceTypeName === "离心机进水压力"
);
const outletPressure = this.systemData.find(
(item) => item.deviceTypeName === "离心机出水压力"
);
//
if (inletPressure && outletPressure) {
// -
const pressureDiff =
parseFloat(outletPressure.curValue) -
parseFloat(inletPressure.curValue);
// 11
const status =
inletPressure.status === 1 || outletPressure.status === 1 ? 1 : 0;
//
const pressureDiffObj = {
paramType: "14", //
curTime: null,
orderNum: this.systemData.length + 1, // +1
deviceTypeName: "离心机压差",
otherName: "压差计算值",
deviceName: "离心机压差",
curValue: parseFloat(pressureDiff.toFixed(2)), //
status: status,
};
// systemData
this.systemData.push(pressureDiffObj);
console.log("添加离心机压差对象:", pressureDiffObj);
} else {
console.warn("找不到进水压力或出水压力数据");
}
},
//
getTempDisplay(deviceName, data) {
// console.log("this.systemData",this.systemData)
// console.log("deviceName",deviceName)
const item = data.find((item) => item.deviceTypeName === deviceName);
// console.log("item", item);
if (!item) return "--℃";
if (item.status === 1) {
return '<span style="color: red">异常</span>';
}
return `${item.curValue}`;
},
// -
getDeviceData(deviceTypeName) {
return (
this.systemData.find(
(item) => item.deviceTypeName === deviceTypeName
) || {}
);
},
goEnergy() {
// this.$router.push("/comprehensiveEnergy/systemEnergy");
},
},
};
</script>
<style lang="scss" scoped>
.project-all {
width: 100%;
display: flex;
flex-direction: row;
align-items: flex-start;
padding: 0.35rem;
.project-img {
width: 1.4rem;
height: 1.4rem;
border-radius: 0.1rem;
border: solid 1px #0163a8;
margin-right: 0.4rem;
cursor: pointer;
}
.project-li {
width: calc((100% - 0.7rem) / 2);
display: flex;
flex-direction: column;
.list-con {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
.project-left {
display: flex;
flex-direction: row;
align-items: center;
.left-icon {
width: 0.2rem;
height: 0.2rem;
}
.project-name {
font-family: SourceHanSansCN-Regular;
font-size: 0.18rem;
line-height: 0.4rem;
color: #ffffff;
opacity: 0.8;
margin-left: 0.15rem;
}
}
.project-right {
font-family: SourceHanSansCN-Regular;
font-size: 0.18rem;
line-height: 0.4rem;
color: #ffffff;
}
}
}
.project-li:nth-last-child(1) {
margin-left: 1.5rem;
}
}
.project-data {
display: flex;
flex-direction: row;
align-items: stretch;
justify-content: space-between;
margin: 16px 0;
.overview {
width: 100%;
padding: 30px 20px 5px 20px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
.overview-li {
cursor: pointer;
width: 49%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
background-position: center center;
margin-bottom: 15px;
font-family: SourceHanSansCN-Regular;
font-size: 15px;
color: #ffffff;
/* 从 rgba(41, 128, 185, 0.8) 渐变到透明 */
background: linear-gradient(
to bottom,
rgba(31, 100, 146, 0.6) 0%,
rgba(33, 65, 87, 0.3) 50%,
rgba(40, 62, 77, 0) 100%
);
border-radius: 8px;
padding: 5px;
color: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
.overview-details {
font-family: DIN-Bold;
font-size: 20px;
color: #15e1fd;
margin-top: 5px;
}
}
}
}
.chartsDiv {
width: 100%;
height: 4rem;
}
.project-bie {
width: 100%;
display: flex;
flex-direction: row;
align-items: stretch;
justify-content: space-between;
.pressure {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
margin-top: 25px;
}
.hot-tem {
width: 100%;
padding: 0 20px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: stretch;
flex-wrap: wrap;
margin-top: 25px;
.tem-li {
background-color: rgba(93, 125, 143, 0.5);
width: 32%;
padding: 10px;
border-radius: 10px;
display: flex;
flex-direction: column;
justify-content: space-between;
margin-bottom: 20px;
.tem-title {
font-size: 15px;
font-weight: 600;
color: #ffffff;
}
.tem-detail {
margin-top: 8px;
font-size: 20px;
font-weight: 600;
color: #f89615;
text-align: center;
}
}
}
.bir-right {
width: 49.4%;
display: flex;
flex-direction: column;
justify-content: space-between;
.special-div {
width: 100%;
margin-bottom: 20px;
}
.later-data {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
.special-div {
width: 49%;
margin-bottom: 0;
}
.hot-data {
width: 100%;
padding: 25px 25px 5px 25px;
.hot-li {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
.hot-title {
font-size: 15px;
font-weight: 600;
color: #ffffff;
}
.hot-detail {
font-size: 20px;
font-weight: 600;
color: #f5f127;
}
}
}
}
}
}
@media (max-width: 1485px) {
}
// 2000px
@media (min-width: 2000px) {
}
</style>
Loading…
Cancel
Save