Compare commits

..

129 Commits
dev ... bl_ai

Author SHA1 Message Date
25604 3326753c5e 添加ai策略控制建议 2 weeks ago
25604 344f3d62a7 ai功能优化 2 weeks ago
selia-zx cc92182808 修改菜单icon、能效日历展示 2 weeks ago
selia-zx cda0d5e510 修改各个系统页面的样式 2 weeks ago
25604 72dacfd492 Merge branch 'bl_ai' of https://git.mhito.net/lijianfu/eemcs_ui into bl_ai 2 weeks ago
selia-zx 6e277f9422 能管平台 2 weeks ago
selia-zx 978ab794d0 添加数据导出、加减载实时记录页面 2 weeks ago
selia-zx d376767755 1、首页添加仪表盘,展示各个设备组的eer; 2 weeks ago
25604 eef3af6e60 还原中央空调系统相关代码 2 weeks ago
25604 2c8e50d253 添加ai助手以及对话生成报表 3 weeks ago
selia-zx 65a43438c9 1.设备监控热量表弹框添加累计热量 1 month ago
selia-zx 527f22af11 去掉设备监控页面温度T1, T7, 压力P10 P12;点击热量表显示进水温度、出水温度、瞬时热量、瞬时流量 1 month ago
selia-zx 6e21f179bf 修改首页热量的单位 1 month ago
selia-zx 8e18b64273 修改系统监控热量表数据 1 month ago
selia-zx 590d355787 修改系统标题 1 month ago
selia-zx e18f0fbf65 修系统监控下发指令时延迟两秒更新数据 1 month ago
selia-zx 9a77cd2dd7 系统监控页面添加温度、压力传感器、热量表的标志 1 month ago
selia-zx bcecb61b77 1.修改首页所有数据异常状态的字段为1 1 month ago
selia-zx bd13df93d3 1.对接设备监控页面-二通阀、三通阀的开度设置 2 months ago
selia-zx 042a25075f 1.对接首页 2 months ago
25604 62f69a129f 采暖系统报表增加设定温度 3 months ago
selia-zx 3cb840553c 设备能耗、能源总览导出添加系统类型参数 3 months ago
selia-zx 36ca430781 修改表格和分页问题 3 months ago
selia-zx f8ad817a15 1.修改采暖系统运行记录页面并对接 4 months ago
selia-zx 5d17762698 修改能源总览、系统能耗分析页面去掉系统条件,默认传参"冷源监控系统"参数"0"; 4 months ago
selia-zx 97310bf723 1.首页热水系统修改区分高中区、裙楼中厨平均供回水温度 4 months ago
selia-zx b1dc33207f 生活水箱水位值减去0.1再计算百分比 4 months ago
selia-zx b4d866d2ad 1.生活水箱水位值减去0.1 4 months ago
selia-zx 249ee124ec 蒸汽采暖系统运行监测页面优化报警信息 4 months ago
selia-zx 411d17da66 添加蒸汽机运行记录页面 4 months ago
selia-zx 9c44565d0f 对接蒸汽采暖运行监测页面 4 months ago
selia-zx b0d836e4b3 1.对接采暖泵监测列表 4 months ago
selia-zx daee6003a2 1.修改1号螺杆机和2号离心机的底图 4 months ago
selia-zx 9652f1a8bf 1.中央热水监测页面-添加一个生活自来水液位监测 4 months ago
selia-zx 73fb410dc1 2.修改综合能耗报表-热水热泵运行记录表头 4 months ago
selia-zx 66070c30e0 修改响应超时时间为15秒 5 months ago
selia-zx f1b163985e 1.修改设备延时开关最后一组数据的定位问题 5 months ago
selia-zx a8ff9d7354 对接热泵运行记录 5 months ago
selia-zx f813b951de 1.修改风柜监测系统-新风阀的数据部分样式改为绿色 5 months ago
v-lijf43 221b93d7f0 大屏中间图片设置改成0.8 5 months ago
selia-zx 355b4b76c8 修改生活热水监控的侧边样式 5 months ago
v-lijf43 914dba815c 大屏改成图片可点击返回 5 months ago
v-lijf43 2167d233dd 主机详情修改远程启动信号 5 months ago
selia-zx a6dac60428 修改图表、input输入框手机端样式 5 months ago
v-lijf43 46dbc7a1e4 修改文字标识 5 months ago
zx-student 01247b6aa7 主机详情-冷水设定值偏移输入范围校验问题 5 months ago
zx-student 3f5dde3aed 主机详情-添加冷水设定值偏移输入范围校验、添加最终出水温度设定值、屏蔽偏移值定时设置 5 months ago
v-lijf43 e27df7aab2 冷定值绑点修改 5 months ago
selia-zx 7dd6e3591b 修改主机详情-偏移值显示除以10,修改乘以10 5 months ago
selia-zx 99789a5a44 1.修改模式选择里面的选择事件 5 months ago
v-lijf43 1c2cfafd46 Merge branch 'meizhou' of https://git.mhito.net/lijianfu/eemcs_ui into meizhou 5 months ago
v-lijf43 728f3f81bd 优化当出现8quality出现可以控制问题 5 months ago
v-lijf43 6ab6c37564 优化 5 months ago
selia-zx 16710f719c 修改风柜水阀、风机的远程手自动状态 5 months ago
v-lijf43 532698dc42 新风阀新增三种模式、水阀手自动、风机频率手自动 5 months ago
v-lijf43 db172a2345 主阀,副阀修改 5 months ago
selia-zx c40c0da5a3 修改风柜监测-风阀手动调节的状态 5 months ago
selia-zx f931ee6941 修改热泵运行状态 5 months ago
selia-zx 30ccdd2779 修改能耗报表查询-图表请求分页问题 5 months ago
25604 7dd55779bc 主机详情页优化 5 months ago
selia-zx f4d65ef7d5 1.风柜监测系统两个页面-添加每个点位是否正常采集的数据 5 months ago
selia-zx e94a6b8c16 1.新增综合能耗报表菜单,可查询系统能耗报表 5 months ago
selia-zx 37913a8f71 热水监测修改刷新请求函数 6 months ago
25604 850b7aa51b 风柜系统loading添加修复 6 months ago
selia-zx 7232da0589 修改甲方要求 6 months ago
25604 e941ef8fb3 修改ahu绑定值,以及启停状态 6 months ago
selia-zx dc740bd707 Merge branch 'meizhou' of https://git.mhito.net/lijianfu/eemcs_ui into meizhou 6 months ago
selia-zx 09e837ad12 修改大屏页面-标题添加返回首页事件 6 months ago
v-lijf43 5881fb5a75 火焰百分比添加 6 months ago
selia-zx b7e2a43f55 调整大屏图标的大小 6 months ago
selia-zx 2ba2a398c3 修改热水控制文字表达 6 months ago
selia-zx 1fa123a2f7 1.修改生活热水、风机、温度系统的监测页面改动 6 months ago
selia-zx f20e9edd11 1.进入系统直接大屏展示F11效果 6 months ago
selia-zx 2e6cc09510 1风柜监测系统添加操作loading,修改请求更新数据时间 6 months ago
selia-zx 9146a2c5fe 风柜监测-添加风机频率调节 6 months ago
selia-zx 8efdbb0a83 1.修改风柜监测系统样式、loading问题 6 months ago
selia-zx b5a7b0feaa 1.修改蒸汽锅炉-燃气阀状态 6 months ago
25604 e91b527cf9 蒸汽锅炉界面优化 6 months ago
selia-zx 1d35fb16f5 1.修改风柜监测系统 6 months ago
v-lijf43 277a81df49 添加回风温度设置、ahu水阀调节 6 months ago
selia-zx 950219588d 修改风柜监测系统页面 6 months ago
selia-zx 90b1317425 修改风柜系统流程图 6 months ago
25604 12d0be108b 蒸汽锅炉、热水锅炉数据绑定 6 months ago
selia-zx ab72f8510c 添加热水锅炉静态页面 6 months ago
selia-zx 97f715ea6b 添加蒸汽锅炉监测、采暖泵监测静态页面 6 months ago
selia-zx 13d0d71bd7 生活热水-控制界面,去掉供水温度设定、温度偏差值设置;水箱添加最低液位设置值功能 7 months ago
selia-zx 4fb6d980bf 修改input单位样式 7 months ago
selia-zx 8cdad84735 1.主机参数详情页面冷水设定值便宜可编辑 7 months ago
selia-zx 2e63ef4fea 1.修改主机运行记录导出异常 7 months ago
selia-zx ff44d006db 1.测试修改主机运行记录功能 7 months ago
selia-zx b63bc3ed98 一些图表添加单位 7 months ago
selia-zx a41b7e1cf8 1.主机参数启动停止颜色分别用绿色和蓝色; 7 months ago
selia-zx 54929bc638 修改能耗分析图表参数 8 months ago
selia-zx 1629c56cc5 处理能耗报表报错问题 8 months ago
selia-zx 2c51fbf984 修改大屏数据 8 months ago
selia-zx 61dc4a5d59 修改生活热水-热泵开关机逻辑 8 months ago
selia-zx b422e6e323 1.修改大屏数据 8 months ago
selia-zx b31574e5fd 修改能源分析-能耗报表数据 8 months ago
selia-zx 8416806539 1.修改热水-项目总览,饼图描述 9 months ago
selia-zx 68efeef754 更新2000px以上页面样式变动2.0 9 months ago
selia-zx 56da047b0f 修改设备定时开关匹配数据逻辑 9 months ago
selia-zx b829c8d52d 更新2000px以上页面样式变动 9 months ago
selia-zx 46fe8ea70c 1.生活热水-首页修改热泵总数 9 months ago
selia-zx 7e78020d6e 1.对接生活热水供水监控系统-项目总览-项目概况、当月数据统计 9 months ago
selia-zx eec0e3cabb 1.生活热水供水监控系统添加:项目总览、能源分析(能耗报表、温度变化表、水位变化表)、用能查询(能耗、水表读数、电表读数)、数据分析四个静态页面内容 9 months ago
selia-zx 928427732c 修改首页饼图数据 9 months ago
selia-zx d99ad9947d 修改首页饼图数据 9 months ago
selia-zx bf2640958b 1.首页添加生活热水、温度系统、风柜系统的数据 9 months ago
25604 c90530caad 1、风柜系统添加定时; 9 months ago
selia-zx b71839eba6 1.压缩机排序 9 months ago
selia-zx eb9d523e1e 1.修改风柜系统监测-定时开关机参数问题 9 months ago
selia-zx 027e4d6d7f 1.修改大屏数据 9 months ago
selia-zx 781cc7e59f 1.修改风柜系统监测-定时开关机弹框高亮 9 months ago
selia-zx ad60088e00 1.主机详情页面,图表添加负载y轴,压缩机参数不采用tab形式 9 months ago
selia-zx 8282126f15 1.冷源监控-系统监测添加可点击打开弹窗控制阀门、水泵、风机、主机 10 months ago
mh ee447e1d7f 监控界面、主机详情、温湿度图表优化 10 months ago
selia-zx a9833153fb 1.修改中央空调-系统监测-压缩机参数内容tab分开展示 10 months ago
selia-zx c06ca6002c 1.空调风柜监控系统-系统监控添加定时开关功能 10 months ago
selia-zx e6b93684ae 1.修改生活热水-实时监控的动态图 10 months ago
selia-zx abec1b1416 1.ps修改生活热水-实时监控的动态图,修改添加供水泵1、供水泵2、补水状态、回水状态、热泵等状态 10 months ago
mh ac91f0837c 冷热源界面修改 10 months ago
v-lijf43 f9a2a9b905 阀门开关到位修改 10 months ago
selia-zx 39661b7ba6 1.联调梅州-中央空调-系统监测页面内容,调整样式 10 months ago
selia-zx ccde8e6187 修改主机参数页面数据 10 months ago
selia-zx 4f79f5bac9 1.解决冲突 10 months ago
selia-zx c6b4345766 系统监测采用大屏显示版本 10 months ago
selia-zx a74c3ad306 修改定时任务、表单构建样式 11 months ago
selia-zx 11d131c3ec 1.修改系统监测页面 11 months ago
selia-zx e068a1a14b 梅州分支 11 months ago
  1. 8
      .env.development
  2. 4
      .env.production
  3. 1
      .gitignore
  4. 0
      git
  5. 4
      package.json
  6. 216
      src/App.vue
  7. 239
      src/api/ai.js
  8. 27
      src/api/boiler/boilerRunReport.js
  9. 27
      src/api/boiler/heatingPump.js
  10. 27
      src/api/boiler/heatingRunPeport.js
  11. 10
      src/api/boiler/hotwaterBoiler.js
  12. 10
      src/api/boiler/steamBoiler.js
  13. 27
      src/api/centerairC/hostRunReport.js
  14. 27
      src/api/comprehensiveEnergy/hotPumpLog.js
  15. 19
      src/api/comprehensiveEnergy/meterRecord.js
  16. 9
      src/api/comprehensiveEnergy/systemEnergy.js
  17. 15
      src/api/device/gather.js
  18. 18
      src/api/heatRecoverySys/deviceMonitor.js
  19. 0
      src/api/heatRecoverySys/thermalAnalysis.js
  20. 8
      src/api/index.js
  21. 7
      src/api/region.js
  22. BIN
      src/assets/flowimg/circulate-move - 副本.gif
  23. BIN
      src/assets/flowimg/circulate-move.gif
  24. BIN
      src/assets/flowimg/circulate-nomove - 副本.png
  25. BIN
      src/assets/flowimg/circulate-nomove.png
  26. BIN
      src/assets/flowimg/coolWaterBox.png
  27. BIN
      src/assets/flowimg/supply-move.gif
  28. BIN
      src/assets/flowimg/supply-nomove.png
  29. BIN
      src/assets/flowimg/waterBox.png
  30. 5
      src/assets/icons/svg/ai.svg
  31. 1
      src/assets/icons/svg/base.svg
  32. 1
      src/assets/icons/svg/bg-calendar.svg
  33. 1
      src/assets/icons/svg/boilerSys.svg
  34. 1
      src/assets/icons/svg/change.svg
  35. 1
      src/assets/icons/svg/co2.svg
  36. 1
      src/assets/icons/svg/collect.svg
  37. 1
      src/assets/icons/svg/ddevice.svg
  38. 1
      src/assets/icons/svg/deng.svg
  39. 1
      src/assets/icons/svg/energy.svg
  40. 1
      src/assets/icons/svg/heatRecoverySys.svg
  41. 1
      src/assets/icons/svg/heatingPump.svg
  42. 1
      src/assets/icons/svg/hotPumpLog.svg
  43. 1
      src/assets/icons/svg/hz-icon.svg
  44. 1
      src/assets/icons/svg/meterRecord.svg
  45. 1
      src/assets/icons/svg/sys.svg
  46. 1
      src/assets/icons/svg/waterPump.svg
  47. BIN
      src/assets/images/alarm.png
  48. BIN
      src/assets/images/badValve.png
  49. BIN
      src/assets/images/blade1.png
  50. BIN
      src/assets/images/blade2.png
  51. BIN
      src/assets/images/boiler.png
  52. BIN
      src/assets/images/boilerWork.gif
  53. BIN
      src/assets/images/detail-line.png
  54. 0
      src/assets/images/fenggui1.png
  55. BIN
      src/assets/images/fenggui2.png
  56. BIN
      src/assets/images/flowchart.png
  57. BIN
      src/assets/images/gasStop.png
  58. BIN
      src/assets/images/gasWork.png
  59. BIN
      src/assets/images/green-out.png
  60. BIN
      src/assets/images/heatingPump.png
  61. BIN
      src/assets/images/host-img2222.png
  62. BIN
      src/assets/images/host-img4.png
  63. BIN
      src/assets/images/host-img6.png
  64. BIN
      src/assets/images/hot-icon1.png
  65. BIN
      src/assets/images/hot-icon2.png
  66. BIN
      src/assets/images/hot-icon3.png
  67. BIN
      src/assets/images/hot-icon4.png
  68. BIN
      src/assets/images/hotWater.png
  69. BIN
      src/assets/images/meizhou.png
  70. BIN
      src/assets/images/monitor-border.png
  71. BIN
      src/assets/images/monitor-hot.png
  72. BIN
      src/assets/images/noalarm (2).png
  73. BIN
      src/assets/images/steamHeating.png
  74. BIN
      src/assets/images/stopValve.png
  75. BIN
      src/assets/images/temp1.png
  76. BIN
      src/assets/images/temp2.png
  77. BIN
      src/assets/images/temp3.png
  78. BIN
      src/assets/images/temp4.png
  79. BIN
      src/assets/images/temp5.png
  80. BIN
      src/assets/images/temp6.png
  81. BIN
      src/assets/images/whiteFan.png
  82. BIN
      src/assets/images/workValve.png
  83. 4
      src/assets/styles/bigScreen.scss
  84. 40
      src/assets/styles/element-ui.scss
  85. 7
      src/assets/styles/index.scss
  86. 6
      src/assets/styles/ruoyi.scss
  87. 14
      src/assets/styles/sidebar.scss
  88. 24
      src/layout/components/Navbar.vue
  89. 100
      src/permission.js
  90. 12
      src/router/index.js
  91. 192
      src/store/modules/permission.js
  92. 53
      src/utils/fullscreen.js
  93. 2
      src/utils/request.js
  94. 381
      src/views/ai/COMPONENT_GUIDE.md
  95. 384
      src/views/ai/README.md
  96. 700
      src/views/ai/components/AirConditioningAISaving.vue
  97. 536
      src/views/ai/components/AirConditioningReport.vue
  98. 527
      src/views/ai/components/ComprehensiveReport.vue
  99. 625
      src/views/ai/components/LightingAISaving.vue
  100. 467
      src/views/ai/components/LightingReport.vue
  101. Some files were not shown because too many files have changed in this diff Show More

8
.env.development

@ -7,9 +7,11 @@ ENV = 'development'
# 开发环境 # 开发环境
# VUE_APP_BASE_API = '/dev-api' # VUE_APP_BASE_API = '/dev-api'
# 后台 # 后台
# VUE_APP_BASE_API = 'http://192.168.1.222:8080' VUE_APP_BASE_API = 'http://192.168.1.222:8080'
# 海湾酒店-云端 # 梅州云端
VUE_APP_BASE_API = 'http://106.55.173.225:8090' # VUE_APP_BASE_API = 'http://106.55.173.225:8091'
# 广合
# VUE_APP_BASE_API = 'http://106.55.173.225:8092'
# 路由懒加载 # 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true VUE_CLI_BABEL_TRANSPILE_MODULES = true

4
.env.production

@ -7,4 +7,6 @@ ENV = 'production'
# 生产环境 # 生产环境
# VUE_APP_BASE_API = '/prod-api' # VUE_APP_BASE_API = '/prod-api'
# 后台 # 后台
VUE_APP_BASE_API = 'http://106.55.173.225:8090' # VUE_APP_BASE_API = 'http://106.55.173.225:8091'
# 广合
VUE_APP_BASE_API = 'http://106.55.173.225:8092'

1
.gitignore vendored

@ -21,3 +21,4 @@ selenium-debug.log
package-lock.json package-lock.json
yarn.lock yarn.lock
dist.rar

4
package.json

@ -59,7 +59,8 @@
"vue-meta": "2.4.0", "vue-meta": "2.4.0",
"vue-router": "3.4.9", "vue-router": "3.4.9",
"vuedraggable": "2.24.3", "vuedraggable": "2.24.3",
"vuex": "3.6.0" "vuex": "3.6.0",
"xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "4.4.6", "@vue/cli-plugin-babel": "4.4.6",
@ -72,6 +73,7 @@
"connect": "3.6.6", "connect": "3.6.6",
"eslint": "7.15.0", "eslint": "7.15.0",
"eslint-plugin-vue": "7.2.0", "eslint-plugin-vue": "7.2.0",
"html-webpack-plugin": "^5.6.3",
"lint-staged": "10.5.3", "lint-staged": "10.5.3",
"runjs": "4.4.2", "runjs": "4.4.2",
"sass": "1.32.13", "sass": "1.32.13",

216
src/App.vue

@ -2,27 +2,235 @@
<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 { mapState } from "vuex";
export default { export default {
name: "App", name: "App",
components: { ThemePicker }, components: { ThemePicker },
metaInfo() { data() {
return { return {
title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title, showAIDialog: false,
titleTemplate: title => { AIComponent: null,
return title ? `${title} - ${process.env.VUE_APP_TITLE}` : process.env.VUE_APP_TITLE robotPosition: { x: window.innerWidth - 90, y: window.innerHeight - 140 },
isDragging: false,
dragOffset: { x: 0, y: 0 },
hasMoved: false,
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() {
// Esc 退
window.addEventListener("keydown", this.handleKeyDown);
//
window.addEventListener("mousemove", this.onDrag);
window.addEventListener("mouseup", this.endDrag);
//
window.addEventListener("resize", this.handleResize);
},
beforeDestroy() {
window.removeEventListener("keydown", this.handleKeyDown);
window.removeEventListener("mousemove", this.onDrag);
window.removeEventListener("mouseup", this.endDrag);
window.removeEventListener("resize", this.handleResize);
},
methods: {
handleKeyDown(event) {
if (event.key === "Escape" && isFullscreen()) {
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>
<style scoped> <style scoped>
#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>

239
src/api/ai.js

@ -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 - 超时时间毫秒默认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();
},
};
}
/**
* 获取 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,
},
});
}

27
src/api/boiler/boilerRunReport.js

@ -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",
});
}

27
src/api/boiler/heatingPump.js

@ -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,
});
}

27
src/api/boiler/heatingRunPeport.js

@ -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",
});
}

10
src/api/boiler/hotwaterBoiler.js

@ -0,0 +1,10 @@
import request from "@/utils/request";
// 热水锅炉设备参数列表
export function hotWaterBoiler(query) {
return request({
url: "/device/hotWaterBoiler/list",
method: "get",
params: query,
});
}

10
src/api/boiler/steamBoiler.js

@ -0,0 +1,10 @@
import request from "@/utils/request";
// 蒸汽锅炉设备参数列表
export function steamBoilerBoiler(query) {
return request({
url: "/device/steamBoiler/list",
method: "get",
params: query,
});
}

27
src/api/centerairC/hostRunReport.js

@ -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',
});
}

27
src/api/comprehensiveEnergy/hotPumpLog.js

@ -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,
});
};

19
src/api/comprehensiveEnergy/meterRecord.js

@ -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",
});
};

9
src/api/comprehensiveEnergy/systemEnergy.js

@ -0,0 +1,9 @@
import request from "@/utils/request";
export const compreReport = (data) => {
return request({
url: "/compre/report",
method: "post",
data,
});
};

15
src/api/device/gather.js

@ -8,6 +8,12 @@ export function cpmList(query) {
params: query, params: query,
}); });
} }
export function cpmList2(cpmIds) {
return request({
url: "/device/cpm/list/" + cpmIds,
method: "get",
});
}
// 根据id查询设备采集参数信息 // 根据id查询设备采集参数信息
export function getCPM(cpmId) { export function getCPM(cpmId) {
@ -42,3 +48,12 @@ export function delCPM(cpmIds) {
method: "delete", method: "delete",
}); });
} }
// 设备组列表
export function devicesList(query) {
return request({
url: "/space/tree",
method: "get",
params: query,
});
}

18
src/api/heatRecoverySys/deviceMonitor.js

@ -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,
});
}

0
src/api/heatRecoverySys/thermalAnalysis.js

8
src/api/index.js

@ -45,3 +45,11 @@ export function viewMainParams() {
method: "get", method: "get",
}); });
} }
// 获取热回收系统相关数据
export function ersDatas(query) {
return request({
url: "/pro/overview/ersDatas",
method: "get",
params: query,
});
}

7
src/api/region.js

@ -7,6 +7,13 @@ export function spaceTree() {
method: "get", method: "get",
}); });
} }
export function floorTree() {
return request({
url: "/space/floorTree",
method: "get",
});
}
// 获取区域信息 // 获取区域信息
export function getAreaList(query) { export function getAreaList(query) {

BIN
src/assets/flowimg/circulate-move - 副本.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
src/assets/flowimg/circulate-move.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 24 KiB

BIN
src/assets/flowimg/circulate-nomove - 副本.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/flowimg/circulate-nomove.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
src/assets/flowimg/coolWaterBox.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

BIN
src/assets/flowimg/supply-move.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

After

Width:  |  Height:  |  Size: 468 KiB

BIN
src/assets/flowimg/supply-nomove.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 186 KiB

BIN
src/assets/flowimg/waterBox.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 188 KiB

5
src/assets/icons/svg/ai.svg

@ -0,0 +1,5 @@
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="currentColor"/>
<path d="M691.2 422.4c-13.4-17.8-35.4-28.8-58.6-28.8H391.4c-23.2 0-45.2 11-58.6 28.8-13.4 17.8-17.2 41-10.2 62.6l76.8 236.8c10.6 32.6 41 54.6 75.2 54.6h64.8c34.2 0 64.6-22 75.2-54.6L691.2 485c7-21.6 3.2-44.8-10-62.6zM512 672c-26.6 0-48-21.4-48-48s21.4-48 48-48 48 21.4 48 48-21.4 48-48 48zm96-192H416v-64h192v64z" fill="currentColor"/>
<circle cx="512" cy="384" r="64" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 675 B

1
src/assets/icons/svg/base.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772762000168" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16378" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M272.718 223.441H751.52q47.88 0 47.88 47.88 0 47.881-47.88 47.881H272.718q-47.88 0-47.88-47.88t47.88-47.88zM272.718 446.883H751.52q47.88 0 47.88 47.88t-47.88 47.88H272.718q-47.88 0-47.88-47.88t47.88-47.88z" p-id="16379"></path><path d="M176.957 766.085h718.205a127.68 127.68 0 0 0 84.908-34.474 127.68 127.68 0 0 0 42.773-93.207V127.681A127.68 127.68 0 0 0 895.162 0H129.077A127.68 127.68 0 0 0 1.397 127.68v702.245a17.875 17.875 0 0 0 0 5.746 85.546 85.546 0 0 0 0 12.768 175.561 175.561 0 0 0 175.56 173.007h798.005a47.88 47.88 0 0 0 47.88-47.88 48.519 48.519 0 0 0-22.982-40.858 47.242 47.242 0 0 0-24.898-7.022H176.957a79.8 79.8 0 0 1 0-159.601zM94.603 609.037V156.41a63.84 63.84 0 0 1 63.84-63.84h707.352a63.84 63.84 0 0 1 63.84 63.84v452.628a63.84 63.84 0 0 1-63.84 63.84H158.444a63.84 63.84 0 0 1-63.84-63.84z" p-id="16380"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
src/assets/icons/svg/bg-calendar.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772761945332" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15333" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M0 941.611997A82.388003 82.388003 0 0 0 82.388003 1024h858.980243A82.388003 82.388003 0 0 0 1023.756249 941.611997V363.433468H0z m694.69174-429.490121h73.125446a36.562723 36.562723 0 0 1 0 73.125446h-73.125446a36.562723 36.562723 0 0 1 0-73.125446z m-219.376339 0h73.125446a36.562723 36.562723 0 0 1 0 73.125446h-73.125446a36.562723 36.562723 0 0 1 0-73.125446z m0 221.813854h73.125446a36.562723 36.562723 0 0 1 0 73.125446h-73.125446a36.562723 36.562723 0 0 1 0-73.125446z m-219.376339-221.813854h73.125446a36.562723 36.562723 0 0 1 0 73.125446h-73.125446a36.562723 36.562723 0 0 1 0-73.125446z m0 221.813854h73.125446a36.562723 36.562723 0 0 1 0 73.125446h-73.125446a36.562723 36.562723 0 0 1 0-73.125446zM1023.756249 155.757201a82.388003 82.388003 0 0 0-82.388003-82.388003H804.37991V41.194001a40.95025 40.95025 0 0 0-82.388003 0v32.175197H301.764342V41.194001a40.95025 40.95025 0 0 0-82.388003 0v32.175197H82.388003A82.388003 82.388003 0 0 0 0 155.757201v128.213282h1023.756249z" fill="" p-id="15334"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
src/assets/icons/svg/boilerSys.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1757379307805" class="icon" viewBox="0 0 1335 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1517" xmlns:xlink="http://www.w3.org/1999/xlink" width="260.7421875" height="200"><path d="M1233.548986 825.047188V692.461449C1293.801739 651.456928 1335.652174 562.176 1335.652174 458.722319c0-137.676058-73.935768-250.078609-167.179131-257.335652v-0.415537l-165.917681-25.822608V115.533913h-56.750376v50.665739L810.889275 139.664696V75.790841h-60.697971v54.494608L514.285449 87.010319V0h-66.337391v76.636754l-105.768812-16.458203v0.296811c60.549565 8.221681 103.231072 167.045565 96.048232 359.780174-7.182841 192.734609-61.677449 347.863188-122.672232 351.573334l87.188406-2.849392v40.158609l-159.016811 8.102956v66.841971l370.554434-30.660637 1.409855 25.555478-195.628521 13.341681 23.240348 11.353044v113.975652l861.792463-106.733449v-84.027363l-34.934724-4.541217-36.61171 2.700985z m-553.360696-32.218898v-34.489507l160.975768-6.678261v32.085333l-160.975768 9.082435zM528.517565 204.503188l27.870609-13.623652v-0.845913l1.409855 0.148406 0.430377-0.148406v0.281971l147.737971 23.552v295.787594l-145.91258-4.11084-31.551072 3.546898V204.503188z m-53.099594 600.791189l8.310725-39.179131 125.19513-4.11084 2.122203 36.478145-135.642899 6.826666z m204.770319 38.882319l269.148753-24.976696v-40.870957l-43.52742 2.270609 4.927073-31.22458 231.824695-14.766376 15.909102 93.822145-478.133797 42.859594-0.148406-27.098899z m-406.854493-73.579595c-56.008348-2.122203-97.458087-133.179362-99.980985-302.955594l-35.008928-2.700985c-21.563362 8.934029-17.066667 54.895304-17.066667 54.895304H0c0-138.150957 105.160348-144.814377 105.160348-144.814377l70.848927 1.27629c12.184116-180.134957 65.402435-319.280232 123.785276-316.994782 62.033623 2.404174 106.421797 163.528348 99.135072 359.973101-7.271884 196.429913-63.562203 353.740058-125.595826 351.321043zM250.227014 320.853333c-11.620174 0-21.696928 22.973217-26.742724 56.869102l30.942609 0.712348-0.281971 92.620058-30.245102-1.27629c4.897391 32.204058 14.988986 54.168116 26.327188 54.168116 16.384 0 29.681159-45.516058 29.68116-101.539247 0-56.023188-13.297159-101.554087-29.68116-101.554087z" fill="" p-id="1518"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

1
src/assets/icons/svg/change.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772761902255" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12543" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M480 384c52.9 0 96 43.1 96 96s-43.1 96-96 96-96-43.1-96-96 43.1-96 96-96z m0-64c-88.4 0-160 71.6-160 160s71.6 160 160 160 160-71.6 160-160-71.6-160-160-160zM259.1 85c3-0.7 5.6 2.1 4.7 5.1l-13.3 42.8L197 305.5c-0.9 2.9-4.7 3.8-6.8 1.5l-41.1-44.4c-7.2-7.8-20-6.5-25.5 2.6-10.2 16.9-19.2 34.6-27 52.9C75 369.3 64 423.8 64 480s11 110.7 32.7 161.9c15.8 37.5 36.9 72.1 62.7 103.2 10.2 12.3 9.6 30.4-1.3 42.1-13.2 14.2-35.8 13.5-48.1-1.4C41.3 702.8 0 596.2 0 480c0-100.3 30.8-193.4 83.4-270.4 4.2-6.1 3.5-14.4-1.5-19.8l-44.7-48.4c-2.1-2.3-0.9-5.9 2.1-6.6l145-32.8 74.8-17z m742.3 186.8c3-0.6 5.5 2.4 4.5 5.3L956 417.4l-25.6 72.1c-1 2.9-4.8 3.6-6.8 1.3l-28.1-33-118.2-138.7c-2-2.3-0.7-6 2.3-6.5l53.7-9.9c10.7-2 16.4-13.9 11.1-23.5-18.8-34.2-42.4-65.4-70.3-93.4-38.2-38.2-82.7-68.2-132.3-89.2C590.7 75 536.2 64 480 64c-41.2 0-81.5 5.9-120.2 17.6-15.3 4.6-31.7-2.9-38.3-17.5-7.9-17.6 1.4-38.2 19.9-43.7C385.2 7.1 431.8 0 480 0c192 0 357.7 112.8 434.5 275.7 3.1 6.5 10.2 10.2 17.3 8.9l69.6-12.8zM917.7 544c20.5 0 35.6 18.9 31.2 38.9C901.8 798.6 709.8 960 480 960c-29.3 0-58-2.6-85.9-7.7-7.2-1.3-14.3 2.5-17.4 9.1l-26.9 59.1c-1.3 2.8-5.1 3.2-6.9 0.7L262 907.8l-50-70.2c-1.8-2.5-0.2-6 2.9-6.3l36.7-3.5 187.7-18.1c3.1-0.3 5.3 2.8 4 5.6l-25.2 55.3c-4.5 9.9 1.9 21.3 12.7 22.5 16.2 1.9 32.6 2.8 49.1 2.8 56.2 0 110.7-11 161.9-32.7 49.5-20.9 94-51 132.3-89.2 38.2-38.2 68.2-82.7 89.2-132.3 10-23.6 17.7-47.9 23.1-72.7 3.3-14.5 16.3-25 31.3-25z" p-id="12544"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

1
src/assets/icons/svg/co2.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

1
src/assets/icons/svg/collect.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772761836584" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8587" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M938.592 32C985.76 32 1024 69.376 1024 115.456v584.32c0 22.144-8.992 43.392-25.024 59.04-16 15.648-37.76 24.448-60.384 24.448H597.376v125.28h85.248c23.584 0 42.72 18.688 42.72 41.728 0 23.04-19.136 41.728-42.72 41.728H341.376c-23.584 0-42.72-18.688-42.72-41.728 0-23.04 19.136-41.728 42.72-41.728h85.248v-125.28H85.376a86.4 86.4 0 0 1-60.384-24.448A82.432 82.432 0 0 1 0 699.808V115.456C0 69.376 38.24 32 85.408 32h853.184zM608 544c-17.696 0-32 14.304-32 32v96a31.968 31.968 0 1 0 64 0v-96c0-17.696-14.304-32-32-32z m128-64c-17.696 0-32 14.304-32 32v160a31.968 31.968 0 1 0 64 0v-160c0-17.696-14.304-32-32-32z m128-64c-17.696 0-32 14.304-32 32v224a31.968 31.968 0 1 0 64 0v-224c0-17.696-14.304-32-32-32zM384.32 224H268.896L192 416h115.008l-51.296 128L448 352h-115.008l51.296-128z" fill="" p-id="8588"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
src/assets/icons/svg/ddevice.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772761795781" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7589" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M928 678c0 9.389-7.611 17-17 17H109c-9.389 0-17-7.611-17-17v-66c0-9.389 7.611-17 17-17h802c9.389 0 17 7.611 17 17v66zM928 828c0 9.389-7.611 17-17 17H109c-9.389 0-17-7.611-17-17v-66c0-9.389 7.611-17 17-17h802c9.389 0 17 7.611 17 17v66zM911 173H109c-9.389 0-17 7.611-17 17v338c0 9.389 7.611 17 17 17h802c9.389 0 17-7.611 17-17V190c0-9.389-7.611-17-17-17zM432 338c0 9.389-7.611 17-17 17H189c-9.389 0-17-7.611-17-17v-66c0-9.389 7.611-17 17-17h226c9.389 0 17 7.611 17 17v66z" p-id="7590"></path></svg>

After

Width:  |  Height:  |  Size: 829 B

1
src/assets/icons/svg/deng.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772762690987" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18333" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M362.666667 853.333333h298.666666v85.333334H362.666667v-85.333334zM512 85.333333C324.266667 85.333333 170.666667 238.933333 170.666667 426.666667c0 117.333333 59.733333 221.866667 149.333333 281.6V768c0 23.466667 19.2 42.666667 42.666667 42.666667h298.666666c23.466667 0 42.666667-19.2 42.666667-42.666667v-59.733333c89.6-61.866667 149.333333-164.266667 149.333333-281.6 0-187.733333-153.6-341.333333-341.333333-341.333334z m59.733333 448l-17.066666 17.066667V682.666667h-85.333334v-132.266667l-17.066666-17.066667-106.666667-106.666666 59.733333-59.733334 106.666667 106.666667 85.333333-85.333333 59.733334 59.733333-85.333334 85.333333z" fill="" p-id="18334"></path></svg>

After

Width:  |  Height:  |  Size: 1009 B

1
src/assets/icons/svg/energy.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772761782154" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6524" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M511.8 63.4c-247.7 0-448.5 200.8-448.5 448.5s200.8 448.5 448.5 448.5 448.5-200.8 448.5-448.5S759.5 63.4 511.8 63.4zM749.6 466c-6.6 0-13-0.7-19.1-1.9l-96 149.7c4.8 10.1 7.5 21.3 7.5 33.3 0 42.5-34.5 77-77 77s-77-34.5-77-77c0-9.6 1.8-18.9 5-27.4l-80.6-89.8c-10.6 6.2-23 9.8-36.1 9.8-6.6 0-13-0.9-19.1-2.6l-68.2 89c2.9 6.9 4.5 14.5 4.5 22.5 0 32.4-26.3 58.7-58.7 58.7-32.4 0-58.7-26.3-58.7-58.7 0-32.4 26.3-58.7 58.7-58.7 5.9 0 11.6 0.9 17 2.5l64.8-84.7c-7.7-11.4-12.1-25.2-12.1-40 0-39.7 32.2-71.8 71.8-71.8s71.8 32.2 71.8 71.8c0 8-1.3 15.6-3.7 22.8l81.1 90.3C537 574 550.6 570 565 570c11.9 0 23.2 2.7 33.3 7.5l87.5-136.4c-18.8-17.3-30.6-42.1-30.6-69.7 0-52.2 42.3-94.5 94.5-94.5s94.5 42.3 94.5 94.5-42.4 94.6-94.6 94.6z" fill="" p-id="6525"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
src/assets/icons/svg/heatRecoverySys.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769656588898" class="icon" viewBox="0 0 1034 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5427" xmlns:xlink="http://www.w3.org/1999/xlink" width="201.953125" height="200"><path d="M390.656 0c37.888 0 69.12 30.208 69.12 67.584V112.64h115.2v-45.056c0-37.376 30.72-67.584 69.12-67.584h206.848c37.888 0 69.12 30.208 69.12 67.584V112.64h28.672c47.616 0 86.016 37.888 86.016 84.48v732.16c0 46.592-38.4 84.48-86.016 84.48H86.016C38.4 1013.76 0 975.872 0 929.28v-732.16C0 150.528 38.4 112.64 86.016 112.64h28.672v-45.056c0-37.376 30.72-67.584 69.12-67.584h206.848z m48.128 744.448c-7.168-2.56-15.36 1.024-17.92 8.192v0.512c-14.848 40.96-11.264 62.464 5.632 89.6l4.096 6.656c3.072 4.608 5.12 10.24 6.144 15.36 3.072 18.944-9.728 35.84-27.648 47.104-6.656 3.584-9.216 12.288-5.12 18.944 3.584 6.656 12.288 9.216 18.944 5.12 0.512 0 0.512-0.512 1.024-0.512 26.624-16.384 45.568-41.984 40.448-74.752-1.536-10.24-5.12-17.92-12.288-29.696l-4.096-6.656c-10.752-18.432-11.776-31.744-1.024-61.952 3.072-7.168-0.512-15.36-8.192-17.92 0.512 0 0 0 0 0z m82.944 0c-7.168-2.56-15.36 1.024-17.92 8.192v0.512c-14.848 40.96-11.264 62.464 5.632 89.6l4.096 6.656c3.072 4.608 5.12 10.24 6.144 15.36 3.072 18.944-9.728 35.84-27.648 47.104-6.656 3.584-9.216 12.288-5.12 18.944 3.584 6.656 12.288 9.216 18.944 5.12 0.512 0 0.512-0.512 1.024-0.512 26.624-16.384 45.568-41.984 40.448-74.752-1.536-10.24-5.12-17.92-12.288-29.696l-4.096-6.656c-10.752-18.432-11.776-31.744-1.024-61.952 3.072-7.168-0.512-15.36-8.192-17.92 0.512 0 0.512 0 0 0z m64 8.192c-15.36 40.96-11.776 62.464 6.144 89.6l4.096 6.656c3.072 4.608 5.632 10.24 6.656 15.36 3.072 18.944-10.24 35.84-28.672 47.104-6.656 3.584-8.704 11.776-5.12 18.432 0 0 0 0.512 0.512 0.512 4.096 6.656 13.312 8.192 19.968 4.096 27.136-16.384 47.104-41.984 41.472-74.752-1.536-10.24-5.12-17.92-12.8-29.696l-4.096-6.656c-11.264-18.432-12.288-31.744-1.024-61.952 2.56-7.168-0.512-14.848-7.68-17.92h-0.512c-7.68-2.048-15.872 2.048-18.944 9.216zM445.952 194.048H135.68c-3.584 0-5.632 4.096-6.144 12.8v439.808h323.072v-435.2c0-9.728-1.536-15.872-4.608-16.896l-2.048-0.512z m452.608 0h-310.784c-3.584 0-5.632 4.096-6.144 12.8v439.808h323.072v-435.2c0-9.728-1.536-15.872-4.608-16.896l-1.536-0.512z m-48.128-126.464h-206.848V112.64h206.848v-45.056z m-459.776 0H183.808V112.64h206.848v-45.056z" p-id="5428"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

1
src/assets/icons/svg/heatingPump.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1757380960233" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3007" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M428.8 704h153.6c236.8-134.4 57.6-339.2 25.6-358.4 12.8 25.6 12.8 64-6.4 76.8-38.4-128-121.6-160-121.6-160 12.8 70.4-38.4 147.2-83.2 204.8 0-25.6-6.4-44.8-19.2-70.4-6.4 44.8-44.8 89.6-51.2 140.8-19.2 64 6.4 115.2 102.4 166.4z m0 0" fill="" p-id="3008"></path><path d="M1024 76.8H512c-57.6 0-115.2 12.8-166.4 32-51.2 25.6-102.4 57.6-140.8 96-38.4 38.4-70.4 83.2-96 140.8-19.2 51.2-32 108.8-32 166.4 0 57.6 12.8 115.2 32 166.4 6.4 19.2 12.8 32 25.6 51.2H0v217.6h512c57.6 0 115.2-12.8 166.4-32s96-51.2 140.8-96c38.4-38.4 70.4-83.2 96-140.8 25.6-51.2 32-108.8 32-166.4 0-76.8-19.2-153.6-57.6-217.6H1024V76.8M460.8 896H51.2v-115.2h185.6c-12.8-12.8-32-32-44.8-51.2-38.4-64-64-134.4-64-217.6 0-211.2 172.8-384 384-384h460.8v115.2h-179.2l32 38.4C870.4 352 896 428.8 896 512c0 211.2-172.8 384-384 384h-51.2z m0 0" fill="" p-id="3009"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
src/assets/icons/svg/hotPumpLog.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1761181457603" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5548" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M670.208 223.053H193.183v0.017c-24.374 0.412-44.02 20.636-44.02 45.549 0 24.914 19.64 45.139 44.02 45.55v0.016h477.025c24.719 0 44.765-20.402 44.765-45.566 0-25.163-20.046-45.566-44.765-45.566zM395.732 439.798h-202.55v0.023c-24.374 0.406-44.02 20.635-44.02 45.543 0 24.914 19.64 45.143 44.02 45.55v0.015h202.556c24.719 0 44.76-20.395 44.76-45.565 0.001-25.164-20.04-45.566-44.766-45.566zM276.261 658.512h-83.083v0.022c-24.374 0.406-44.014 20.636-44.014 45.544 0 24.913 19.64 45.144 44.014 45.55v0.017h83.083c24.719 0 44.759-20.397 44.759-45.566 0-25.165-20.041-45.567-44.759-45.567z m0 0" p-id="5549"></path><path d="M715.201 1.954H148.93C67.141 1.954 0.84 68.257 0.84 150.043v724.501c0 81.786 66.301 148.09 148.09 148.09h241.713v-0.084c0.695 0.033 1.386 0.049 2.081 0.049 25.164 0 45.566-20.396 45.566-45.566 0-25.163-20.402-45.565-45.566-45.565-0.695 0-1.392 0.023-2.081 0.056v-0.021H148.93c-31.455 0-56.958-25.503-56.958-56.957V150.043c0-31.461 25.503-56.958 56.958-56.958h566.271c31.46 0 56.957 25.497 56.957 56.958V320.46c0 25.164 20.402 45.566 45.566 45.566 25.169 0 45.566-20.402 45.566-45.566V150.043c0.001-81.786-66.302-148.089-148.089-148.089z m0 0" p-id="5550"></path><path d="M676.148 840.226c-7.364 0-14.695-2.041-21.226-6.091-11.942-7.409-19.073-20.219-19.073-34.271V652.755c0-14.056 7.131-26.865 19.073-34.269 11.942-7.41 26.593-8.099 39.181-1.853l128.683 73.556c13.817 6.856 22.405 20.697 22.405 36.121 0 15.417-8.588 29.263-22.405 36.12l-128.683 73.557c-5.707 2.831-11.841 4.239-17.955 4.239z m0 0" p-id="5551"></path><path d="M726.854 430.13c-163.575 0-296.181 132.605-296.181 296.18s132.605 296.18 296.181 296.18c163.574 0 296.179-132.604 296.179-296.18S890.429 430.13 726.854 430.13z m0 501.227c-113.248 0-205.048-91.805-205.048-205.047s91.8-205.047 205.048-205.047c113.241 0 205.047 91.805 205.047 205.047s-91.805 205.047-205.047 205.047z m0 0" p-id="5552"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

1
src/assets/icons/svg/hz-icon.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1758105515572" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7076" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M480.256 16.384C219.648 16.384 8.192 227.84 8.192 488.448c0 260.608 211.456 472.064 472.064 472.064 260.608 0 472.064-211.456 472.064-472.064 0.512-260.608-210.944-472.064-472.064-472.064z m0 881.664c-225.792 0-409.088-183.296-409.088-409.088 0-225.792 183.296-409.088 409.088-409.088 225.792 0 409.088 183.296 409.088 409.088 0.512 225.792-182.784 409.088-409.088 409.088z" p-id="7077"></path><path d="M203.264 281.088h50.688v160.256h199.68V281.088h50.688v383.488h-50.688V484.864h-199.68v179.712h-50.688V281.088zM562.176 625.664l160.768-199.68h-152.576v-40.448H778.24v40.448l-158.72 198.144h165.888l-7.68 40.448h-215.552v-38.912z" p-id="7078"></path></svg>

After

Width:  |  Height:  |  Size: 990 B

1
src/assets/icons/svg/meterRecord.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1761011231553" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2521" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M925.425633 514.798111a38.910648 38.910648 0 0 0-25.59911-9.727662 38.398666 38.398666 0 0 0-28.415013 12.543564l-138.747178 153.594662a62.973812 62.973812 0 0 0-62.973812 62.973812 38.398666 38.398666 0 0 0 0 5.631804l-28.926995 31.742897a38.398666 38.398666 0 0 0 56.830026 51.198221l24.319155-26.879066a69.373589 69.373589 0 0 0 10.239644 0 62.973812 62.973812 0 0 0 63.229802-63.229802A66.045705 66.045705 0 0 0 793.590215 716.775092l106.74829-117.243926a214.008563 214.008563 0 1 1-167.674173-80.893189 38.398666 38.398666 0 0 0 0-76.797331 291.061886 291.061886 0 1 0 192.50531 72.701474z" p-id="2522"></path><path d="M365.317097 732.902532a367.859217 367.859217 0 0 1 367.603226-367.603226 112.124104 112.124104 0 0 1 38.398666 6.655769v-207.352795a38.398666 38.398666 0 0 0-38.398666-38.398666H455.681957V63.997776A63.997776 63.997776 0 0 0 391.684181 0H64.015567A63.997776 63.997776 0 0 0 0.017791 63.997776v921.567976A38.398666 38.398666 0 0 0 38.416457 1023.964417h470.51165a366.835252 366.835252 0 0 1-143.61101-291.061885zM293.639588 793.572423H167.179982a38.398666 38.398666 0 0 1 0-76.797331h126.459606a38.398666 38.398666 0 0 1 0 76.797331z m0-255.991104H167.179982a38.398666 38.398666 0 0 1 0-76.797331h126.459606a38.398666 38.398666 0 0 1 0 76.797331z m0-255.991104H167.179982a38.398666 38.398666 0 0 1 0-76.797332h126.459606a38.398666 38.398666 0 0 1 0 76.797332z" p-id="2523"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

1
src/assets/icons/svg/sys.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772761773062" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5506" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M514.4 63.4c-1.2-0.1-2.4-0.2-3.6-0.2-1.3 0-2.5 0.1-3.7 0.2C262.2 65.9 64.4 264.8 63.5 510v1.6c0 247.5 200.7 448.2 448.2 448.2s448.2-200.7 448.2-448.2c0-246.6-199.2-446.7-445.5-448.2z m28 320.3c48.3-2.1 95.1-9.9 139.8-22.6 10.6 37.7 17.2 77.1 19.5 117.7H542.4v-95.1z m0-64.3V128.7c2 0.2 4.1 0.3 6.1 0.5 47.6 49 86.1 106.9 112.6 171-38 10.6-77.8 17.2-118.7 19.2z m-63.2-190.5V319c-39.7-2.6-78.3-9.4-115.2-20 27.3-63.8 66.3-121.4 114.5-170 0.2-0.1 0.5-0.1 0.7-0.1z m0 254.4v95.5H321.4c2.5-41.2 9.6-81.2 20.8-119.4 43.9 13 89.7 21.1 137 23.9z m-222.1 95.5H129c5.8-68.3 29.5-131.6 66.3-185.1 27.4 16.9 56.2 31.7 86.3 44.2-13.4 45.1-21.8 92.2-24.5 140.9z m-0.2 63.2c2.4 50 10.8 98.3 24.5 144.4-29.8 12.4-58.3 27-85.5 43.8-37.7-54.2-61.7-118.6-67.1-188.2h128.1z m64.3 0h158v99c-47.4 2.7-93.4 10.9-137.2 23.9-11.5-39.3-18.6-80.5-20.8-122.9z m158 163.4v189c-1-0.1-2.1-0.2-3.1-0.3-47.3-48.3-85.7-105.4-112.6-168.5 37.1-10.8 75.8-17.6 115.7-20.2z m63.2 189.1V704.9c40 2 78.9 8.3 116.2 18.4-27.6 64.4-67.2 122.4-116.2 171.2 0.1 0 0 0 0 0z m0-253.9V542h159.1c-2.5 41.7-9.7 82.1-21 120.7-44.2-12.4-90.4-20-138.1-22.1zM765.7 542h128.9c-5.4 68.4-28.6 131.7-65.1 185.3-28-16.9-57.6-31.7-88.3-44 13.5-45.1 21.9-92.4 24.5-141.3z m0.2-63.2c-2.3-47.8-10.1-94.3-22.9-138.6 30.3-12.2 59.4-26.8 87-43.6 35.7 52.8 58.7 115 64.4 182.1H765.9z m23.4-232.7c-21.5 12.6-44 23.7-67.3 33.3-18.1-44.6-41.4-86.5-69.1-125.1 52 20.6 98.4 52.2 136.4 91.8z m-415.7-93c-28 38.3-51.7 80-70.3 124.3-23.1-9.8-45.4-21.1-66.7-33.8 38.2-39.3 84.9-70.4 137-90.5zM237.2 780.3c21-12.5 43-23.6 65.7-33.2 18.2 43.5 41.2 84.5 68.5 122.3-50.9-20.1-96.5-50.7-134.2-89.1z m409.9 90.8c29-39.1 53.4-81.8 72.4-127.3 23.9 9.7 47 21 69.1 33.9-39.2 40.9-87.5 73.1-141.5 93.4z" fill="" p-id="5507"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

1
src/assets/icons/svg/waterPump.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772762705576" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19399" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512.001204 0C229.230529 0 0 229.230529 0 512.001204c0 282.771879 229.230529 512 512.001204 512s512-229.228121 512-512C1024.002408 229.230529 794.771879 0 512.001204 0z m237.209231 446.997545c-6.559008 0.443127-13.109587 1.034365-21.077452 1.670156 0.449148 5.038166 0.390145 8.990188 1.201743 12.756771 26.124046 121.339843-44.146568 234.231366-164.596544 263.191188-15.224075 3.661822-31.352468 4.806969-47.07747 4.903301-78.408263 0.479252-156.818935 0.317896-235.230811 0.180622-43.727523-0.075861-77.845925-32.180924-78.66234-73.57119-0.873009-44.25735 31.17907-77.686977 76.120378-79.231903 8.50612-0.291404 17.033914-0.042145 28.422769-0.042145-11.620053-45.103867-11.819942-85.77044-0.229992-126.671822 24.958429-88.058326 109.503043-154.520212 201.029323-155.502799 78.914007-0.848926 157.844872-0.470823 236.766103-0.155335 40.000677 0.160152 73.384547 33.519939 74.35148 73.126859 1.037978 42.575151-29.37164 76.532197-71.017187 79.346297z" fill="" p-id="19400"></path><path d="M619.382534 417.442873c-9.322534-8.887835-16.803921-11.215458-26.496129 0.243239-12.172758 14.393212-26.009652 27.382384-39.033744 40.88332-6.639686-3.349947-11.382835-7.155063-16.646177-8.125608-13.862181-2.549187-15.851438-11.222683-15.244546-23.303926 0.797148-15.853846 0.62375-31.80282-0.187848-47.656667-0.187847-3.696742-4.586609-10.25575-7.13098-10.294283-38.861551-0.6093-73.978194 8.897468-100.370765 40.000677-5.771494 6.802246-5.141723 12.188412 1.485922 18.306701 14.644879 13.515386 28.751503 27.612376 42.878597 41.28069-3.508895 7.064751-7.651173 12.266682-8.636168 18.009275-2.427568 14.118665-11.211846 15.944158-23.250943 15.355328-14.830318-0.727307-29.729273-0.14209-44.598124-0.09874-7.311602 0.021675-13.370888 1.306503-12.801325 10.898767 1.976011 33.403136 8.962493 64.994026 33.116549 90.143915 14.725557 15.333653 17.00381 15.212034 31.643872 0.503334 5.421086-5.443965 11.096247-10.736206 15.816518-16.75455 11.663402-14.864034 23.337642-23.68805 43.443344-11.683873 7.745096 4.628755 11.562254 7.29354 11.335873 16.248807-0.51056 19.920263-0.160152 39.863404-0.160152 63.439468 15.496214-1.625603 28.729828-1.996482 41.519111-4.572159 24.036049-4.840685 45.522912-15.014553 62.524314-33.535593 7.906453-8.614493 9.600692-15.673224-0.552705-24.176935-10.580871-8.861344-19.076153-20.388677-30.169993-28.420362-15.531134-11.244358-5.16701-21.881824-2.178308-33.499468 3.08985-12.004177 11.945174-8.902285 19.557813-8.993801 19.705924-0.237218 39.419073-0.079474 59.141855-0.079474 0-10.620608 0.740553-18.382562-0.131252-25.957873-3.40895-29.634145-12.479816-56.804598-34.874609-78.160209z m-74.716338 94.103162c-0.54548 19.766131-17.918964 36.328018-38.093303 36.313569-21.413409-0.01445-36.509845-15.096436-36.564031-36.535132-0.056595-22.076896 14.143952-36.527907 36.112475-36.75188 21.765021-0.221564 39.112014 16.417389 38.544859 36.973443z" fill="" p-id="19401"></path></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
src/assets/images/alarm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
src/assets/images/badValve.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
src/assets/images/blade1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
src/assets/images/blade2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
src/assets/images/boiler.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
src/assets/images/boilerWork.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

BIN
src/assets/images/detail-line.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

0
src/assets/images/fenggui.png → src/assets/images/fenggui1.png

Before

Width:  |  Height:  |  Size: 576 KiB

After

Width:  |  Height:  |  Size: 576 KiB

BIN
src/assets/images/fenggui2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 KiB

BIN
src/assets/images/flowchart.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

BIN
src/assets/images/gasStop.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
src/assets/images/gasWork.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
src/assets/images/green-out.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
src/assets/images/heatingPump.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

BIN
src/assets/images/host-img2222.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

BIN
src/assets/images/host-img4.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 531 KiB

After

Width:  |  Height:  |  Size: 307 KiB

BIN
src/assets/images/host-img6.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

BIN
src/assets/images/hot-icon1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
src/assets/images/hot-icon2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
src/assets/images/hot-icon3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
src/assets/images/hot-icon4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
src/assets/images/hotWater.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

BIN
src/assets/images/meizhou.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

BIN
src/assets/images/monitor-border.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
src/assets/images/monitor-hot.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
src/assets/images/noalarm (2).png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
src/assets/images/steamHeating.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

BIN
src/assets/images/stopValve.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
src/assets/images/temp1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
src/assets/images/temp2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
src/assets/images/temp3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
src/assets/images/temp4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
src/assets/images/temp5.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
src/assets/images/temp6.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
src/assets/images/whiteFan.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
src/assets/images/workValve.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

4
src/assets/styles/bigScreen.scss

@ -679,10 +679,6 @@
.el-card__header { .el-card__header {
padding: 0.14rem 0.15rem 0.07rem !important; padding: 0.14rem 0.15rem 0.07rem !important;
min-height: 0.4rem !important; min-height: 0.4rem !important;
}
.el-form-item__error {
font-size: 0.12rem !important;
padding-top: 0.04rem !important;
} }
} }
@media (min-width: 2000px) { @media (min-width: 2000px) {

40
src/assets/styles/element-ui.scss

@ -1430,7 +1430,7 @@
min-width: 50px !important; min-width: 50px !important;
z-index: 0; z-index: 0;
} }
.el-input__suffix{ .el-input__suffix {
z-index: 10; z-index: 10;
} }
.el-cascader__tags { .el-cascader__tags {
@ -1439,3 +1439,41 @@
align-items: center; align-items: center;
flex-wrap: nowrap; flex-wrap: nowrap;
} }
.el-table--group,
.el-table--border {
border: 1px solid #00264f !important;
}
.el-table--border .el-table__cell {
border-right: 1px solid #00264f !important;
}
.el-table--border th.el-table__cell {
border-bottom: 1px solid #00264f !important;
}
.el-table__expand-icon{
color: #fff !important;
}
.el-tag.el-tag--danger {
font-weight: bold !important;
}
.el-tag.el-tag--success{
font-weight: bold !important;
color: #03a249 !important;
}
.el-calendar {
background-color: #637c94 !important;
}
.el-calendar-table td.is-selected .calendar-cell {
color: #409eff !important;
font-weight: bold;
}
.el-calendar-table .el-calendar-day:hover .calendar-cell {
color: #409eff !important;
font-weight: bold;
}
.el-calendar-table thead th {
color: #d8def3 !important;
}
.el-calendar__title{
color: #ffffff !important;
font-weight: bold;
}

7
src/assets/styles/index.scss

@ -775,3 +775,10 @@ table th {
} }
} }
} }
/* 手机端样式 */
@media (min-width: 0px) and (max-width: 990px) {
.app-main {
height: 100% !important;
overflow-y: hidden !important;
}
}

6
src/assets/styles/ruoyi.scss

@ -88,9 +88,9 @@ h6 {
word-break: break-word; word-break: break-word;
} }
.el-dialog:not(.is-fullscreen) { // .el-dialog:not(.is-fullscreen) {
margin-top: 6vh !important; // margin-top: 6vh !important;
} // }
.el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body { .el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body {
overflow: auto; overflow: auto;

14
src/assets/styles/sidebar.scss

@ -116,7 +116,7 @@
} }
& .nest-menu .el-submenu > .el-submenu__title, & .nest-menu .el-submenu > .el-submenu__title,
& .el-submenu .el-menu-item { & .nest-menu .el-menu-item {
min-width: var(--base-sidebar-width) !important; min-width: var(--base-sidebar-width) !important;
padding: 0 35px !important; padding: 0 35px !important;
@ -124,6 +124,9 @@
background-color: rgba(0, 0, 0, 0.06) !important; background-color: rgba(0, 0, 0, 0.06) !important;
} }
} }
& .nest-menu .el-menu > .nest-menu .el-menu-item {
padding: 0 55px !important;
}
& .theme-dark .nest-menu .el-submenu > .el-submenu__title, & .theme-dark .nest-menu .el-submenu > .el-submenu__title,
& .theme-dark .el-submenu .el-menu-item { & .theme-dark .el-submenu .el-menu-item {
@ -328,18 +331,21 @@
} }
} }
} }
.hideSidebar .fixed-header{ .hideSidebar .fixed-header {
width: calc(100% - 0.54rem) !important; width: calc(100% - 0.54rem) !important;
} }
.el-submenu__title { .el-submenu__title {
font-size: 0.14rem !important; font-size: 0.14rem !important;
height: 0.56rem !important; height: 0.56rem !important;
line-height: 0.56rem !important; line-height: 0.56rem !important;
font-size:0.14rem !important; font-size: 0.14rem !important;
padding: 0 0.2rem !important; padding: 0 0.2rem !important;
} }
#app .sidebar-container .nest-menu .el-submenu > .el-submenu__title, #app .sidebar-container .nest-menu .el-submenu > .el-submenu__title,
#app .sidebar-container .el-submenu .el-menu-item { #app .sidebar-container .nest-menu .el-menu-item {
padding: 0 0.35rem !important; padding: 0 0.35rem !important;
} }
#app .sidebar-container .nest-menu .el-menu > .nest-menu .el-menu-item {
padding: 0 0.55rem !important;
}
} }

24
src/layout/components/Navbar.vue

@ -15,6 +15,21 @@
<top-nav id="topmenu-container" class="topmenu-container" v-if="topNav" /> <top-nav id="topmenu-container" class="topmenu-container" v-if="topNav" />
<div class="right-menu"> <div class="right-menu">
<el-form ref="queryForm" size="small" :inline="true">
<el-form-item label="切换项目" prop="project">
<el-select
v-model="project"
placeholder="选择项目"
filterable
>
<el-option label="演示项目1" value="演示项目1" />
<el-option label="演示项目2" value="演示项目2" />
<el-option label="演示项目3" value="演示项目3" />
<el-option label="演示项目4" value="演示项目4" />
<el-option label="演示项目5" value="演示项目5" />
</el-select>
</el-form-item>
</el-form>
<template v-if="device !== 'mobile'"> <template v-if="device !== 'mobile'">
<img <img
v-if="isShowWarning" v-if="isShowWarning"
@ -91,6 +106,7 @@ export default {
data() { data() {
return { return {
isShowWarning: false, isShowWarning: false,
project: "演示项目",
}; };
}, },
mounted() { mounted() {
@ -299,3 +315,11 @@ export default {
} }
} }
</style> </style>
<style scoped>
.el-form-item--small.el-form-item{
margin-bottom: 0 !important;
display: flex;
flex-direction: row;
align-items: center;
}
</style>

100
src/permission.js

@ -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(() => {
isRelogin.show = false;
store.dispatch("GenerateRoutes").then((accessRoutes) => {
// 根据roles权限生成可访问的路由表 // 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表 router.addRoutes(accessRoutes); // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 next({ ...to, replace: true }); // hack方法 确保addRoutes已完成
}) });
}).catch(err => {
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();
}) });

12
src/router/index.js

@ -68,7 +68,7 @@ export const constantRoutes = [
children: [ children: [
{ {
path: "index", path: "index",
component: () => import("@/views/index"), component: () => import("@/views/newSystem/systemHome"),
name: "Index", name: "Index",
meta: { title: "首页", icon: "dashboard", affix: true }, meta: { title: "首页", icon: "dashboard", affix: true },
}, },
@ -91,11 +91,19 @@ export const constantRoutes = [
// 综合大屏 // 综合大屏
{ {
path: "/Screen", path: "/Screen",
name:"Screen", name: "Screen",
hidden: true, hidden: true,
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" },
},
]; ];
// 动态路由,基于用户权限动态去加载 // 动态路由,基于用户权限动态去加载

192
src/store/modules/permission.js

@ -40,6 +40,35 @@ const permission = {
const rewriteRoutes = filterAsyncRouter(rdata, false, true); const rewriteRoutes = filterAsyncRouter(rdata, false, true);
const asyncRoutes = filterDynamicRoutes(dynamicRoutes); const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
rewriteRoutes.push({ path: "*", redirect: "/404", hidden: true }); rewriteRoutes.push({ path: "*", redirect: "/404", hidden: true });
// 空压热回收
const resultERS = sidebarRoutes.find(
(item) => item.name === "HeatRecoverySys"
);
if (resultERS) {
// 定义要添加的多个路由对象数组
const additionalRoutes2 = [
{
path: "/monitorCenter",
name: "monitorCenter",
hidden: true,
component: () =>
import("@/views/heatRecoverySys/deviceMonitor/monitorCenter"),
meta: { title: "系统监测", icon: "screen" },
},
// 可以继续添加更多路由对象
];
// 循环添加额外的路由对象到各路由数组
additionalRoutes2.forEach((route) => {
sidebarRoutes.push(route);
rewriteRoutes.push(route);
asyncRoutes.push(route);
});
} else {
// console.log("不满足条件");
}
// 有中央空调
// 如果动态的路由有中央空调-系统监控-系统监测页面,那就添加一个hidden为true非Layout的大屏页面 // 如果动态的路由有中央空调-系统监控-系统监测页面,那就添加一个hidden为true非Layout的大屏页面
const result = checkRouteArray( const result = checkRouteArray(
sidebarRoutes, sidebarRoutes,
@ -90,6 +119,169 @@ const permission = {
// console.log("不满足条件"); // console.log("不满足条件");
} }
// 风柜
const result2 = sidebarRoutes.find(
(item) => item.name === "AircAndWindc"
);
if (result2) {
// 定义要添加的多个路由对象数组
const additionalRoutes2 = [
{
path: "/asSysMonitorDetails",
name: "asSysMonitorDetails",
hidden: true,
component: () =>
import(
"@/views/aircAndWindc/awSysMonitor/asSysMonitorDetails"
),
meta: { title: "系统监控", icon: "screen" },
},
// 可以继续添加更多路由对象
];
// 循环添加额外的路由对象到各路由数组
additionalRoutes2.forEach((route) => {
sidebarRoutes.push(route);
rewriteRoutes.push(route);
asyncRoutes.push(route);
});
} else {
// console.log("不满足条件");
}
// 锅炉
const result3 = sidebarRoutes.find(
(item) => item.name === "BoilerSys"
);
if (result3) {
// 定义要添加的多个路由对象数组
const additionalRoutes3 = [
{
path: "/boilerMonitorDetails",
name: "boilerMonitorDetails",
hidden: true,
component: () =>
import(
"@/views/boilerSys/boilerMonitor/boilerMonitorDetails"
),
meta: { title: "锅炉监控", icon: "screen" },
},
{
path: "/hotWaterBoilerDetails",
name: "hotWaterBoilerDetails",
hidden: true,
component: () =>
import(
"@/views/boilerSys/hotWaterBoiler/hotWaterBoilerDetails"
),
meta: { title: "热水锅炉监控", icon: "screen" },
},
{
path: "/heatingPumpDetails",
name: "heatingPumpDetails",
hidden: true,
component: () =>
import("@/views/boilerSys/heatingPump/heatingPumpDetails"),
meta: { title: "采暖泵监控", icon: "screen" },
},
{
path: "/steamHeatingDetails",
name: "steamHeatingDetails",
hidden: true,
component: () =>
import("@/views/boilerSys/steamHeating/steamHeatingDetails"),
meta: { title: "蒸汽采暖运行监控", icon: "screen" },
},
// 可以继续添加更多路由对象
];
// 循环添加额外的路由对象到各路由数组
additionalRoutes3.forEach((route) => {
sidebarRoutes.push(route);
rewriteRoutes.push(route);
asyncRoutes.push(route);
});
} else {
// console.log("不满足条件");
}
// 热水
const result4 = sidebarRoutes.find(
(item) => item.name === "HotWater"
);
if (result4) {
// 定义要添加的多个路由对象数组
const additionalRoutes4 = [
{
path: "/waterMonitorDetails",
name: "waterMonitorDetails",
hidden: true,
component: () =>
import("@/views/hotWater/waterMonitor/waterMonitorDetails"),
meta: { title: "热水监控", icon: "screen" },
},
// 可以继续添加更多路由对象
];
// 循环添加额外的路由对象到各路由数组
additionalRoutes4.forEach((route) => {
sidebarRoutes.push(route);
rewriteRoutes.push(route);
asyncRoutes.push(route);
});
} else {
// console.log("不满足条件");
}
// 温度监测
const result5 = sidebarRoutes.find((item) => item.name === "TemSys");
if (result5) {
// 定义要添加的多个路由对象数组
const additionalRoutes5 = [
{
path: "/temMonitorDeatils",
name: "temMonitorDeatils",
hidden: true,
component: () =>
import("@/views/temSys/temMonitor/temMonitorDeatils"),
meta: { title: "温度监测", icon: "screen" },
},
];
// 循环添加额外的路由对象到各路由数组
additionalRoutes5.forEach((route) => {
sidebarRoutes.push(route);
rewriteRoutes.push(route);
asyncRoutes.push(route);
});
} else {
// console.log("不满足条件");
}
//
// 空压热回收
const result6 = sidebarRoutes.find(
(item) => item.name === "NewCenterairC"
);
if (result6) {
// 定义要添加的多个路由对象数组
const additionalRoutes2 = [
{
path: "/monitorCenter",
name: "monitorCenter",
hidden: true,
component: () =>
import("@/views/centerairC/sysMonitor/monitorCenter"),
meta: { title: "系统监测", icon: "screen" },
},
// 可以继续添加更多路由对象
];
// 循环添加额外的路由对象到各路由数组
additionalRoutes2.forEach((route) => {
sidebarRoutes.push(route);
rewriteRoutes.push(route);
asyncRoutes.push(route);
});
} else {
// console.log("不满足条件");
}
commit("SET_ROUTES", rewriteRoutes); commit("SET_ROUTES", rewriteRoutes);
commit("SET_SIDEBAR_ROUTERS", constantRoutes.concat(sidebarRoutes)); commit("SET_SIDEBAR_ROUTERS", constantRoutes.concat(sidebarRoutes));
commit("SET_DEFAULT_ROUTES", sidebarRoutes); commit("SET_DEFAULT_ROUTES", sidebarRoutes);

53
src/utils/fullscreen.js

@ -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,
};

2
src/utils/request.js

@ -17,7 +17,7 @@ const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分 // axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API, baseURL: process.env.VUE_APP_BASE_API,
// 超时 // 超时
timeout: 10000 timeout: 15000
}) })
// request拦截器 // request拦截器

381
src/views/ai/COMPONENT_GUIDE.md

@ -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
**文档状态**: 已完成 ✅

384
src/views/ai/README.md

@ -0,0 +1,384 @@
# AI 智能报表功能实现文档
## 📋 项目概述
基于 mh-ui 能耗监测控制系统,新增 AI 智能报表功能,通过自然语言交互实现智能化的报表生成和数据分析。
## 🎯 功能目标
1. **自然语言交互**: 用户可通过文字描述快速生成所需报表
2. **智能报表生成**: 支持 4 类核心报表的自动生成
3. **可视化展示**: 集成 ECharts 实现丰富的图表展示
4. **前端独立实现**: 报表生成和打印完全在前端完成 (符合项目规范要求)
## 🏗 架构设计
### 技术栈
- **前端框架**: Vue 2.6.12 + Element UI 2.15.14
- **图表库**: ECharts 5.4.0
- **HTTP 客户端**: Axios 0.28.1
- **状态管理**: Vuex 3.6.0
### 系统架构
```
┌─────────────────────────────────────────┐
│ 用户界面层 │
│ ┌───────────┐ ┌──────────────┐ │
│ │ 聊天窗口 │ │ 报表展示区 │ │
│ │ (左侧) │ │ (右侧) │ │
│ └───────────┘ └──────────────┘ │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ 业务逻辑层 │
│ ┌─────────────────────────────────┐ │
│ │ AI 对话处理 / 报表数据格式化 │ │
│ │ 图表渲染 / 打印导出 │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ API 接口层 │
│ ┌───────────┐ ┌──────────────┐ │
│ │ AI 对话 │ │ 报表数据 │ │
│ │ /ai/chat │ │ /ai/reports/ │ │
│ └───────────┘ └──────────────┘ │
└─────────────────────────────────────────┘
```
## 📁 文件结构
### 修改的文件
```
src/
├── api/
│ └── ai.js # AI 相关 API 接口 (已扩展)
├── assets/
│ └── icons/
│ └── svg/
│ └── ai.svg # 新增:AI 机器人图标
└── views/
└── ai/
└── index.vue # 新增:AI报表主页面
```
## 🔧 核心功能实现
### 1. AI 对话接口 (`src/api/ai.js`)
```javascript
/**
* AI 对话接口(实时交互)
*/
export function chat(message, conversationId) {
return request({
url: "/ai/chat",
method: "post",
data: {
message,
conversationId,
},
});
}
/**
* 获取 AI报表数据
*/
export function getReportData(reportType, params) {
return request({
url: `/ai/reports/${reportType}/data`,
method: "get",
params: params,
});
}
```
### 2. 报表类型映射
```javascript
reportTypeMap: {
comprehensive: "项目综合分析报告",
airConditioning: "空调制冷系统分析报告",
lighting: "照明系统分析报告",
pump: "水泵系统分析报告",
}
```
### 3. 报表数据结构
```javascript
{
title: string, // 报表标题
generateTime: string, // 生成时间
summary: string, // 报告摘要
projectInfo: { // 项目情况
description: string,
configTable: [],
configTableColumns: []
},
equipmentOverview: { // 设备概述
description: string,
charts: [] // 设备相关图表
},
operationData: { // 运行数据
charts: [], // 运行数据图表
dataTable: [], // 数据表格
tableColumns: []
},
analysisSummary: { // 分析总结
points: [], // 分析要点
suggestions: [] // 优化建议
}
}
```
### 4. 图表配置示例
```javascript
// 饼图配置
getChartData(refKey, container) {
if (refKey.includes('pie')) {
return {
title: { text: '能耗占比', ... },
tooltip: { trigger: 'item' },
series: [{
type: 'pie',
radius: '50%',
data: [...]
}]
};
}
// 折线图、柱状图类似配置...
}
```
## 💻 功能演示
### 使用流程
1. **进入页面**
- 访问 `/ai` 路由
- 看到欢迎消息和快捷报表按钮
2. **生成报表**
```
方式 1: 点击快捷报表按钮
方式 2: 输入自然语言,如"生成上个月的空调系统分析报告"
方式 3: 点击快捷查询标签
```
3. **查看报表**
- 左侧显示对话历史
- 右侧显示报表详情 (包含图表和表格)
4. **打印/导出**
- 点击"打印"按钮预览并打印
- 点击"导出"按钮下载报表
### 界面布局
```
┌────────────────────────────────────────────┐
│ AI 智能报表 │
├──────────────┬─────────────────────────────┤
│ │ 报表预览 │
│ 聊天窗口 │ [导出] [打印] │
│ ┌────────┐ ├─────────────────────────────┤
│ │ AI 回复 │ │ │
│ │ 用户问 │ │ 报表内容区 │
│ │ AI 回复 │ │ - 报告摘要 │
│ └────────┘ │ - 项目情况 │
│ │ - 设备概述 (图表) │
│ [输入框...] │ - 运行数据 (图表 + 表格) │
│ [发送] │ - 分析总结 │
│ │ │
└──────────────┴─────────────────────────────┘
```
## 🔌 后端接口要求
### 1. AI 对话接口
**请求:**
```http
POST /ai/chat
Content-Type: application/json
{
"message": "生成上个月的空调系统分析报告",
"conversationId": "uuid-xxx-xxx"
}
```
**响应:**
```json
{
"code": 200,
"conversationId": "uuid-xxx-xxx",
"data": {
"content": "好的,正在为您生成空调系统分析报告...",
"needGenerateReport": true,
"reportType": "airConditioning",
"params": {
"startTime": "2024-02-01 00:00:00",
"endTime": "2024-02-29 23:59:59"
}
}
}
```
### 2. 报表数据接口
**请求:**
```http
GET /ai/reports/airConditioning/data?startTime=2024-02-01&endTime=2024-02-29
```
**响应:**
```json
{
"code": 200,
"data": {
"summary": "本月空调系统运行正常,能效比平均值为 6.2...",
"projectInfo": {
"description": "项目包含 3 个冷站,8 台冷水机组...",
"configTable": [...],
"configTableColumns": [
{"prop": "name", "label": "设备名称"},
{"prop": "model", "label": "型号"}
]
},
"equipmentOverview": {
"description": "主要设备包括冷冻泵、冷却泵、冷水机组...",
"charts": [
{"type": "pie", "data": [...]},
{"type": "line", "data": [...]}
]
},
"operationData": {
"charts": [...],
"dataTable": [...],
"tableColumns": [...]
},
"analysisSummary": {
"points": [
"能效比较上月提升 5.2%",
"用电峰值出现在月中旬"
],
"suggestions": [
"建议在夜间低谷期增加蓄冷",
"优化冷却塔风机运行策略"
]
}
}
}
```
## 🎨 样式特点
### 响应式布局
- 聊天窗口固定宽度 400px
- 报表区域自适应剩余空间
- 支持 1200px 以下分辨率自动调整
### 主题配色
- 主色调:紫色渐变 (#667eea → #764ba2)
- 辅助色:Element UI 标准色
- 图表色:ECharts 默认配色方案
### 动画效果
- 消息出现淡入动画
- 加载状态旋转动画
- 图表渲染平滑过渡
## ✅ 测试验证
### 功能测试清单
- [x] 聊天消息发送和接收
- [x] 快捷报表按钮点击
- [x] 快捷查询标签点击
- [x] 报表数据加载
- [x] ECharts 图表渲染
- [x] 报表打印预览
- [x] 组件销毁时清理资源
### 兼容性测试
- [x] Chrome 90+
- [x] Edge 90+
- [x] Firefox 88+
- [ ] Safari 14+ (待测试)
## 🚀 部署说明
### 开发环境
```bash
# 启动开发服务
npm run dev
# 访问地址
http://localhost:80/ai
```
### 生产环境
```bash
# 构建生产版本
npm run build:prod
# 部署到服务器
将 dist 目录部署到静态服务器
```
## 📝 注意事项
### 开发注意
1. 所有报表生成功能都在前端实现
2. 图表需要根据实际数据调整配置
3. 打印功能会替换页面内容,需重新加载恢复
4. 组件销毁时必须清理 ECharts 实例
### 后端配合
1. 需要实现 AI 对话接口 (`/ai/chat`)
2. 需要实现报表数据接口 (`/ai/reports/{type}/data`)
3. 接口返回数据需符合约定格式
4. 支持跨域请求和 Token 认证
### 性能优化
1. 大数据量时使用降采样策略
2. 图表按需渲染,避免一次性加载过多
3. 使用虚拟滚动优化长列表
4. 合理设置缓存策略
## 🔮 扩展方向
### 功能扩展
- [ ] 支持更多报表类型 (锅炉、热回收等)
- [ ] 报表模板自定义配置
- [ ] 定时自动生成报表
- [ ] 报表对比分析
- [ ] 移动端适配
### 技术优化
- [ ] 接入真实 AI 模型 (如 ChatGPT)
- [ ] 实现离线缓存
- [ ] 添加报表版本管理
- [ ] 支持多人协作编辑
- [ ] 集成语音输入
## 📚 参考资料
- [Vue.js 官方文档](https://cn.vuejs.org/)
- [Element UI 组件库](https://element.eleme.cn/)
- [ECharts 配置手册](https://echarts.apache.org/zh/index.html)
- [项目架构说明文档](./README.md)
## 👥 联系方式
如有问题或建议,请联系:
- 开发团队:铭汉科技前端组
- 技术支持:tech@minghan.com
---
**版本**: v1.0.0
**更新日期**: 2026-03-05
**文档状态**: 已完成 ✅

700
src/views/ai/components/AirConditioningAISaving.vue

@ -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>

536
src/views/ai/components/AirConditioningReport.vue

@ -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>

527
src/views/ai/components/ComprehensiveReport.vue

@ -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>

625
src/views/ai/components/LightingAISaving.vue

@ -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>

467
src/views/ai/components/LightingReport.vue

@ -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>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save