From a10f3b82e33756ed0cd62a0cbe83bab8674df16f Mon Sep 17 00:00:00 2001
From: he wei <858544502@qq.com>
Date: 星期二, 03 六月 2025 08:00:58 +0800
Subject: [PATCH] UA 整理提交

---
 src/components/echarts/BaseChart.vue     |  363 ++
 src/views/user/powerMager.vue            |    6 
 src/styles/blue.css                      |   81 
 src/styles/blue.less                     |  122 
 src/assets/images/img-acdc.png           |    0 
 src/layout/index.vue                     |    4 
 src/utils/excel/Blob.js                  |  204 +
 src/assets/images/img-batt1.png          |    0 
 src/components/echarts/line1.vue         |  246 +
 src/assets/images/img-batt.png           |    0 
 src/components/svg/svgModule.vue         |  107 
 src/views/statistics/power.vue           |  516 +++
 src/utils/getTreeDataByKey.js            |   36 
 src/views/alarm/battAlarm.vue            |  501 +++
 src/views/user/baojiMager.vue            |   12 
 src/icons/svg/arrow.svg                  |    1 
 src/icons/svg/stations.svg               |    1 
 src/utils/excel/Export2Excel.js          |  179 +
 src/components/svg/svgFlow.vue           |  133 
 src/views/realtime/tabs/system.vue       |  158 
 src/components/svg/svgSwitch.vue         |  117 
 src/utils/getQueryString.js              |   11 
 src/api/alarm.js                         |   18 
 src/views/statistics/devWorkstatus.vue   |  416 ++
 src/api/statistic.js                     |   34 
 src/components/svg/svgLock.vue           |   39 
 src/router/index.js                      |    4 
 src/components/svg/svgDiode.vue          |   78 
 src/icons/svg/workstatus.svg             |    1 
 src/router/modules/alarm.js              |   34 
 src/views/alarm/devAlarm.vue             |  495 +++
 src/components/svg/switchBox.vue         |   62 
 src/router/modules/datas.js              |    4 
 src/components/echarts/transparent.js    |  112 
 src/components/svg/svgTextBox.vue        |   68 
 src/utils/export2Excel.js                |  147 
 src/components/svg/svgResistor.vue       |   38 
 src/views/realtime/bar8.vue              |  170 +
 src/components/svg/dotLine.vue           |   53 
 src/views/user/MyCard.vue                |   11 
 package-lock.json                        |  130 
 src/components/card.vue                  |    3 
 src/utils/toFixed.js                     |   16 
 src/components/siteList/ycTree.vue       |  116 
 src/components/svg/svgFloor.vue          |   45 
 src/icons/svg/dev.svg                    |    1 
 src/views/login/index.vue                |    5 
 src/views/alarm/pwrAlarm.vue             |  503 +++
 src/utils/const/const_hrTestType.js      |    7 
 src/views/datas/device.vue               |  185 
 src/utils/const/const_digit.js           |    9 
 src/components/svg/svgTextBorderBox.vue  |   89 
 src/components/svg/svgText.vue           |   63 
 src/icons/svg/dev1.svg                   |    1 
 src/assets/images/img-charge.png         |    0 
 src/utils/exportFile.js                  |   19 
 src/views/statistics/station.vue         |  421 ++
 src/api/station.js                       |   23 
 src/components/siteList/index.vue        |  311 +
 src/components/svg/svgStatus.vue         |   57 
 src/components/svg/svgImg.vue            |   38 
 src/views/realtime/index.vue             |  255 +
 src/router/modules/statistics.js         |   40 
 src/icons/svg/realtime1.svg              |    1 
 src/views/statistics/battHr.vue          |  487 +++
 src/components/svg/stationSvg.vue        |   35 
 src/components/svg/svgCabinet.vue        |  258 +
 src/components/svg/svgStation.vue        |  221 +
 src/components/svgDiagram.vue            |  588 +++
 src/icons/svg/alarm.svg                  |    1 
 src/components/info.vue                  |   54 
 src/views/user/index.vue                 |    4 
 src/assets/images/img-hr.png             |    0 
 src/icons/svg/setting.svg                |    1 
 src/assets/images/img-hr1.png            |    0 
 src/components/svg/svgInputBox.vue       |   82 
 src/components/svg/svgInputBorderBox.vue |  124 
 src/components/svg/svgDotRect.vue        |   66 
 src/icons/svg/tongji.svg                 |    1 
 src/views/realtime/tabs/vol.vue          |   16 
 src/views/statistics/batt.vue            |  516 +++
 src/globalComponents.js                  |    2 
 src/icons/svg/controls.svg               |    1 
 src/components/svg/svgTriangle.vue       |   39 
 package.json                             |    6 
 src/assets/images/img-charge1.png        |    0 
 src/components/svg/svgLine.vue           |  117 
 src/components/svg/svgPv.vue             |   35 
 src/layout/components/AppMain.vue        |   19 
 src/components/svg/svgArc.vue            |   40 
 src/icons/svg/data.svg                   |    2 
 src/views/datas/addEdit.vue              |    2 
 src/components/svg/svgDot.vue            |   25 
 93 files changed, 9,420 insertions(+), 242 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 00823f6..c2c8579 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,14 +12,18 @@
         "axios": "^1.7.2",
         "echarts": "^5.5.0",
         "element-plus": "^2.8.1",
+        "file-saver": "^2.0.5",
         "js-cookie": "^3.0.5",
         "js-md5": "^0.8.3",
         "jsencrypt": "^3.3.2",
+        "moment": "^2.30.1",
         "nprogress": "^0.2.0",
         "path-browserify": "^1.0.1",
         "pinia": "^2.1.7",
+        "pinyin-match": "^1.2.8",
         "vue": "^3.5.13",
-        "vue-router": "^4.3.3"
+        "vue-router": "^4.3.3",
+        "xlsx": "^0.18.5"
       },
       "devDependencies": {
         "@rollup/plugin-commonjs": "^28.0.3",
@@ -1488,6 +1492,15 @@
         "url": "https://github.com/sponsors/antfu"
       }
     },
+    "node_modules/adler-32": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz",
+      "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/ansi-regex": {
       "version": "2.1.1",
       "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-2.1.1.tgz",
@@ -1803,6 +1816,19 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/cfb": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz",
+      "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "adler-32": "~1.3.0",
+        "crc-32": "~1.2.0"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/chalk": {
       "version": "1.1.3",
       "resolved": "https://registry.npmmirror.com/chalk/-/chalk-1.1.3.tgz",
@@ -1909,6 +1935,15 @@
         "node": ">=0.8"
       }
     },
+    "node_modules/codepage": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
+      "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/collection-visit": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/collection-visit/-/collection-visit-1.0.0.tgz",
@@ -1973,6 +2008,18 @@
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
+      }
+    },
+    "node_modules/crc-32": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
+      "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+      "license": "Apache-2.0",
+      "bin": {
+        "crc32": "bin/crc32.njs"
+      },
+      "engines": {
+        "node": ">=0.8"
       }
     },
     "node_modules/crypt": {
@@ -2655,6 +2702,12 @@
         }
       }
     },
+    "node_modules/file-saver": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
+      "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
+      "license": "MIT"
+    },
     "node_modules/fill-range": {
       "version": "4.0.0",
       "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-4.0.0.tgz",
@@ -2730,6 +2783,15 @@
       },
       "engines": {
         "node": ">= 6"
+      }
+    },
+    "node_modules/frac": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz",
+      "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
       }
     },
     "node_modules/fragment-cache": {
@@ -4018,6 +4080,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/moment": {
+      "version": "2.30.1",
+      "resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz",
+      "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/mrmime": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz",
@@ -4428,6 +4499,12 @@
           "optional": true
         }
       }
+    },
+    "node_modules/pinyin-match": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmmirror.com/pinyin-match/-/pinyin-match-1.2.8.tgz",
+      "integrity": "sha512-5rBwNuOjnOtZNEgX4OlTLYsmBcE9XSV1oF/KN9mLEsVNr8HdqMb2YRhR6iqHMeU8ZBKbx/oYBgHr04uIvOlxGg==",
+      "license": "SATA"
     },
     "node_modules/posix-character-classes": {
       "version": "0.1.1",
@@ -5387,6 +5464,18 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/ssf": {
+      "version": "0.11.2",
+      "resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz",
+      "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "frac": "~1.1.2"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/static-extend": {
       "version": "0.1.2",
       "resolved": "https://registry.npmmirror.com/static-extend/-/static-extend-0.1.2.tgz",
@@ -6241,6 +6330,45 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/wmf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz",
+      "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/word": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz",
+      "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/xlsx": {
+      "version": "0.18.5",
+      "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
+      "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "adler-32": "~1.3.0",
+        "cfb": "~1.2.1",
+        "codepage": "~1.15.0",
+        "crc-32": "~1.2.1",
+        "ssf": "~0.11.2",
+        "wmf": "~1.0.1",
+        "word": "~0.3.0"
+      },
+      "bin": {
+        "xlsx": "bin/xlsx.njs"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/zrender": {
       "version": "5.6.1",
       "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
diff --git a/package.json b/package.json
index a25106f..d4aaf1f 100644
--- a/package.json
+++ b/package.json
@@ -13,14 +13,18 @@
     "axios": "^1.7.2",
     "echarts": "^5.5.0",
     "element-plus": "^2.8.1",
+    "file-saver": "^2.0.5",
     "js-cookie": "^3.0.5",
     "js-md5": "^0.8.3",
     "jsencrypt": "^3.3.2",
+    "moment": "^2.30.1",
     "nprogress": "^0.2.0",
     "path-browserify": "^1.0.1",
     "pinia": "^2.1.7",
+    "pinyin-match": "^1.2.8",
     "vue": "^3.5.13",
-    "vue-router": "^4.3.3"
+    "vue-router": "^4.3.3",
+    "xlsx": "^0.18.5"
   },
   "devDependencies": {
     "@rollup/plugin-commonjs": "^28.0.3",
diff --git a/src/api/alarm.js b/src/api/alarm.js
index 9630924..dbf7010 100644
--- a/src/api/alarm.js
+++ b/src/api/alarm.js
@@ -1,12 +1,12 @@
 import request from "@/utils/request";
 
 /**
- * 纭鍛婅
+ * 纭鐢垫睜鍛婅
  * num 
  */
-export function confirmAlm(num) {
+export function confirmBattAlm(num) {
   return request({
-    url: "lockAlm/confirmAlm",
+    url: "alm/updateBattConfrim",
     method: "GET",
     params: {
       num
@@ -15,12 +15,12 @@
 }
 
 /**
- * 鍙栨秷鍛婅
+ * 纭璁惧鍛婅
  * num
  */
-export function cancelAlm(num) {
+export function confirmDevAlm(num) {
   return request({
-    url: "lockAlm/cancleAlm",
+    url: "alm/updateDevConfrim",
     method: "GET",
     params: {
       num
@@ -29,12 +29,12 @@
 }
 
 /**
- * 鍒犻櫎鍛婅
+ * 纭鐢垫簮鍛婅
  * num
  */
-export function delAlm(num) {
+export function confirmPwrAlm(num) {
   return request({
-    url: "lockAlm/delAlm",
+    url: "alm/updatePwrConfrim",
     method: "GET",
     params: {
       num
diff --git a/src/api/station.js b/src/api/station.js
index 5144ef3..7910c53 100644
--- a/src/api/station.js
+++ b/src/api/station.js
@@ -77,6 +77,7 @@
   });
 }
 
+
 /**
  *  鍒犻櫎鏈烘埧
  */
@@ -230,6 +231,18 @@
   });
 }
 
+
+/**
+ * 鍒犻櫎鐢垫睜 璁惧 鐢垫簮 鏈烘埧
+ */
+export function delBatt(stationId, powerId, battgroupId) {
+  return request({
+    url: 'condition/delBatt',
+    method: 'GET',
+    params: { stationId, powerId, battgroupId }
+  });
+}
+
 /**
  * 璁惧涓嬫坊鍔犵數姹犵粍
  */
@@ -239,4 +252,14 @@
     method: 'POST',
     data
   });
+}
+
+/**
+ * 绔欑偣鍒楄〃 鏍戠姸
+ */
+export function getStationTree() {
+  return request({
+    url: 'stationInf/getLeftStation',
+    method: 'GET'
+  });
 }
\ No newline at end of file
diff --git a/src/api/statistic.js b/src/api/statistic.js
new file mode 100644
index 0000000..b19edde
--- /dev/null
+++ b/src/api/statistic.js
@@ -0,0 +1,34 @@
+import request from "@/utils/request";
+
+/**
+ * 绔欑偣淇℃伅缁熻
+ */
+export function getStationStatistic(data) {
+  return request({
+    url: "statistic/getStationStatistic",
+    method: "POST",
+    data
+  });
+}
+
+/**
+ * 钃勭數姹犳牳瀹逛俊鎭粺璁�
+ */
+export function getBattTinfStatistic(data) {
+  return request({
+    url: "statistic/getBattTinfStatistic",
+    method: "POST",
+    data
+  });
+}
+
+/**
+ * 璁惧宸ヤ綔鐘舵�佺粺璁�
+ */
+export function getDeviceStateStatistic(data) {
+  return request({
+    url: "statistic/getDeviceStateStatistic",
+    method: "POST",
+    data
+  });
+}
\ No newline at end of file
diff --git a/src/assets/images/img-acdc.png b/src/assets/images/img-acdc.png
index e115730..8c3bfc0 100644
--- a/src/assets/images/img-acdc.png
+++ b/src/assets/images/img-acdc.png
Binary files differ
diff --git a/src/assets/images/img-batt.png b/src/assets/images/img-batt.png
index 6e6f584..76c0fe5 100644
--- a/src/assets/images/img-batt.png
+++ b/src/assets/images/img-batt.png
Binary files differ
diff --git a/src/assets/images/img-batt1.png b/src/assets/images/img-batt1.png
new file mode 100644
index 0000000..6e6f584
--- /dev/null
+++ b/src/assets/images/img-batt1.png
Binary files differ
diff --git a/src/assets/images/img-charge.png b/src/assets/images/img-charge.png
index a6b7e8a..9e0772a 100644
--- a/src/assets/images/img-charge.png
+++ b/src/assets/images/img-charge.png
Binary files differ
diff --git a/src/assets/images/img-charge1.png b/src/assets/images/img-charge1.png
new file mode 100644
index 0000000..a6b7e8a
--- /dev/null
+++ b/src/assets/images/img-charge1.png
Binary files differ
diff --git a/src/assets/images/img-hr.png b/src/assets/images/img-hr.png
index 45fe16a..e9a8ee3 100644
--- a/src/assets/images/img-hr.png
+++ b/src/assets/images/img-hr.png
Binary files differ
diff --git a/src/assets/images/img-hr1.png b/src/assets/images/img-hr1.png
new file mode 100644
index 0000000..45fe16a
--- /dev/null
+++ b/src/assets/images/img-hr1.png
Binary files differ
diff --git a/src/components/card.vue b/src/components/card.vue
index 1761a87..3419cf6 100644
--- a/src/components/card.vue
+++ b/src/components/card.vue
@@ -44,12 +44,15 @@
     
     .title {
       padding-left: 1.4em;
+      font-size: 24px;
       background: url("@/assets/images/tb1.png") 6px center e('/') auto 82% no-repeat;
     }
     .tools {
       position: absolute;
       top: 6px;
       right: 6px;
+      display: flex;
+      align-items: center;
     }
     .content {
       flex: 1;
diff --git a/src/components/echarts/BaseChart.vue b/src/components/echarts/BaseChart.vue
new file mode 100644
index 0000000..daa57a5
--- /dev/null
+++ b/src/components/echarts/BaseChart.vue
@@ -0,0 +1,363 @@
+<script setup name="BaseChart">
+import * as echarts from "echarts";
+import "./transparent";
+import { onMounted, onBeforeUnmount, ref, nextTick } from "vue";
+
+const chart_instance = ref(null);
+let exportChart_instance = ref(null);
+
+const chart = ref(null);
+const exportChart = ref(null);
+const fullScreenFlag = ref(false);
+const emit = defineEmits(["click"]);
+
+
+  onMounted(() => {
+    console.log('base mounted',chart.value, '=============');
+    
+    chart_instance.value = echarts.init(chart.value, "transparent");
+    exportChart_instance.value = echarts.init(exportChart.value, "transparent");
+
+    let option = getOptions();
+
+    nextTick(() => {
+      chart_instance.value.resize();
+      chart_instance.value.setOption(option);
+    });
+    window.addEventListener("resize", resize);
+
+  });
+
+  onBeforeUnmount(() => {
+    window.removeEventListener("resize", resize);
+    dispose();
+  });
+
+  function getOptions() {
+
+		const option = {
+			// title: {
+			// 	text: '璇锋眰鏁�',
+			// 	textStyle: {
+			// 		fontWeight: 'normal',
+			// 		fontSize: 16,
+			// 		color: '#F1F1F3'
+			// 	},
+			// 	left: '6%'
+			// },
+			tooltip: {
+				trigger: 'axis',
+				axisPointer: {
+					lineStyle: {
+						color: '#57617B'
+					}
+				}
+			},
+			// legend: {
+			// 	icon: 'rect',
+			// 	itemWidth: 14,
+			// 	itemHeight: 5,
+			// 	itemGap: 13,
+			// 	data: ['绉诲姩', '鐢典俊', '鑱旈��'],
+			// 	right: '4%',
+			// 	textStyle: {
+			// 		fontSize: 12,
+			// 		color: '#F1F1F3'
+			// 	}
+			// },
+			grid: {
+				left: '3%',
+				right: '4%',
+				bottom: '3%',
+				containLabel: true
+			},
+			xAxis: [{
+				type: 'category',
+				boundaryGap: false,
+				axisLine: {
+					lineStyle: {
+						color: '#57617B'
+					}
+				},
+				data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55']
+			}, {
+				axisPointer: {
+					show: false
+				},
+				axisLine: {
+					lineStyle: {
+						color: '#57617B'
+					}
+				},
+				axisTick: {
+					show: false
+				},
+
+				position: 'bottom',
+				offset: 20,
+				data: ['', '', '', '', '', '', '', '', '', '', {
+					value: '鍛ㄥ叚',
+					textStyle: {
+						align: 'left'
+					}
+				}, '鍛ㄦ棩']
+			}],
+			yAxis: [{
+				type: 'value',
+				name: '鍗曚綅锛�%锛�',
+				axisTick: {
+					show: false
+				},
+				axisLine: {
+					lineStyle: {
+						color: '#57617B'
+					}
+				},
+				axisLabel: {
+					margin: 10,
+					textStyle: {
+						fontSize: 14
+					}
+				},
+				splitLine: {
+					lineStyle: {
+						color: '#57617B'
+					}
+				}
+			}],
+			series: [{
+				name: '绉诲姩',
+				type: 'line',
+				smooth: true,
+				symbol: 'circle',
+				symbolSize: 5,
+				showSymbol: false,
+				lineStyle: {
+					normal: {
+						width: 1
+					}
+				},
+				areaStyle: {
+					normal: {
+						color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+							offset: 0,
+							color: 'rgba(137, 189, 27, 0.3)'
+						}, {
+							offset: 0.8,
+							color: 'rgba(137, 189, 27, 0)'
+						}], false),
+						shadowColor: 'rgba(0, 0, 0, 0.1)',
+						shadowBlur: 10
+					}
+				},
+				itemStyle: {
+					normal: {
+						color: 'rgb(137,189,27)',
+						borderColor: 'rgba(137,189,2,0.27)',
+						borderWidth: 12
+
+					}
+				},
+				data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
+			}, {
+				name: '鐢典俊',
+				type: 'line',
+				smooth: true,
+				symbol: 'circle',
+				symbolSize: 5,
+				showSymbol: false,
+				lineStyle: {
+					normal: {
+						width: 1
+					}
+				},
+				areaStyle: {
+					normal: {
+						color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+							offset: 0,
+							color: 'rgba(0, 136, 212, 0.3)'
+						}, {
+							offset: 0.8,
+							color: 'rgba(0, 136, 212, 0)'
+						}], false),
+						shadowColor: 'rgba(0, 0, 0, 0.1)',
+						shadowBlur: 10
+					}
+				},
+				itemStyle: {
+					normal: {
+						color: 'rgb(0,136,212)',
+						borderColor: 'rgba(0,136,212,0.2)',
+						borderWidth: 12
+
+					}
+				},
+				data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
+			}, {
+				name: '鑱旈��',
+				type: 'line',
+				smooth: true,
+				symbol: 'circle',
+				symbolSize: 5,
+				showSymbol: false,
+				lineStyle: {
+					normal: {
+						width: 1
+					}
+				},
+				areaStyle: {
+					normal: {
+						color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+							offset: 0,
+							color: 'rgba(219, 50, 51, 0.3)'
+						}, {
+							offset: 0.8,
+							color: 'rgba(219, 50, 51, 0)'
+						}], false),
+						shadowColor: 'rgba(0, 0, 0, 0.1)',
+						shadowBlur: 10
+					}
+				},
+				itemStyle: {
+					normal: {
+
+						color: 'rgb(219,50,51)',
+						borderColor: 'rgba(219,50,51,0.2)',
+						borderWidth: 12
+					}
+				},
+				data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
+			},]
+		};
+
+		return option;
+	}
+
+  function getChart() {
+    return chart_instance.value;
+  }
+
+  function setOption(option) {
+    if (chart_instance.value) {
+      chart_instance.value.setOption(option);
+    }
+  }
+
+  function fullScreen() {
+    fullScreenFlag.value = !fullScreenFlag.value;
+    nextTick(() => {
+      resize();
+    });
+  }
+
+  function getDataURL() {
+    let base64 = "";
+    if (exportChart_instance.value) {
+      let option = chart_instance.value.getOption();
+      option.xAxis[0].axisLine.lineStyle = {
+        color: "#000",
+      };
+      option.xAxis[0].axisLabel.textStyle.color = "#000";
+
+      option.yAxis[0].axisLine.lineStyle = {
+        color: "#000",
+      };
+      option.yAxis[0].axisLabel.textStyle.color = "#000";
+      exportChart_instance.value.setOption(option);
+      base64 = exportChart_instance.value.getDataURL({
+        pixelRatio: 1,
+        backgroundColor: "#fff",
+      });
+    }
+    return base64;
+  }
+
+  function resize() {
+    if (chart_instance.value) {
+      chart_instance.value.resize();
+      console.log('resize', '=============');
+      
+    }
+  }
+
+  function dispose() {
+    disposeChart(chart_instance.value);
+    disposeChart(exportChart_instance.value);
+  }
+
+  function disposeChart(chart) {
+    if (chart) {
+      chart.dispose(); // 閿�姣佸疄渚�
+    }
+  }
+
+  function handleClick() {
+    emit("click", true);
+  }
+
+  defineExpose({
+    getChart,
+    setOption,
+    getDataURL,
+  });
+</script>
+
+<template>
+  <div
+    class="e-chart-root"
+    :class="{ 'full-screen': fullScreenFlag }"
+  >
+    <!-- @click="handleClick"
+    @dblclick="fullScreen" -->
+    <div class="e-chart-container">
+      <div class="e-chart" ref="chart"></div>
+      <slot name="tools"></slot>
+    </div>
+    <div class="export-chart-wrapper">
+      <div class="export-chart" ref="exportChart"></div>
+    </div>
+  </div>
+</template>
+
+
+<style scoped>
+.e-chart-root {
+  position: relative;
+}
+/* chart wrapper css */
+.e-chart-root,
+.e-chart {
+  height: 100%;
+  box-sizing: border-box;
+}
+.e-chart-container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  box-sizing: border-box;
+}
+.e-chart-root.full-screen .e-chart-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-size: 100% 100%;
+  z-index: 9999;
+}
+
+/* export chart wrapper css */
+.export-chart-wrapper {
+  position: relative;
+  width: 0;
+  height: 0;
+  overflow: hidden;
+}
+.export-chart {
+  position: absolute;
+  width: 600px;
+  height: 400px;
+}
+</style>
diff --git a/src/components/echarts/line1.vue b/src/components/echarts/line1.vue
new file mode 100644
index 0000000..facffb1
--- /dev/null
+++ b/src/components/echarts/line1.vue
@@ -0,0 +1,246 @@
+<script setup>
+	import { onMounted, ref, watchEffect, nextTick, onBeforeUnmount } from "vue";
+	import * as echarts from 'echarts';
+  import baseChart from "./BaseChart.vue";
+
+  const chart = ref(null);
+
+
+	function getOptions() {
+
+		const option = {
+			title: {
+				text: '璇锋眰鏁�',
+				textStyle: {
+					fontWeight: 'normal',
+					fontSize: 16,
+					color: '#F1F1F3'
+				},
+				left: '6%'
+			},
+			tooltip: {
+				trigger: 'axis',
+				axisPointer: {
+					lineStyle: {
+						color: '#57617B'
+					}
+				}
+			},
+			// legend: {
+			// 	icon: 'rect',
+			// 	itemWidth: 14,
+			// 	itemHeight: 5,
+			// 	itemGap: 13,
+			// 	data: ['绉诲姩', '鐢典俊', '鑱旈��'],
+			// 	right: '4%',
+			// 	textStyle: {
+			// 		fontSize: 12,
+			// 		color: '#F1F1F3'
+			// 	}
+			// },
+			grid: {
+				left: '3%',
+				right: '4%',
+				bottom: '3%',
+				containLabel: true
+			},
+			xAxis: [{
+				type: 'category',
+				boundaryGap: false,
+				axisLine: {
+					lineStyle: {
+						color: '#57617B'
+					}
+				},
+				data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55']
+			}, {
+				axisPointer: {
+					show: false
+				},
+				axisLine: {
+					lineStyle: {
+						color: '#57617B'
+					}
+				},
+				axisTick: {
+					show: false
+				},
+
+				position: 'bottom',
+				offset: 20,
+				data: ['', '', '', '', '', '', '', '', '', '', {
+					value: '鍛ㄥ叚',
+					textStyle: {
+						align: 'left'
+					}
+				}, '鍛ㄦ棩']
+			}],
+			yAxis: [{
+				type: 'value',
+				name: '鍗曚綅锛�%锛�',
+				axisTick: {
+					show: false
+				},
+				axisLine: {
+					lineStyle: {
+						color: '#57617B'
+					}
+				},
+				axisLabel: {
+					margin: 10,
+					textStyle: {
+						fontSize: 14
+					}
+				},
+				splitLine: {
+					lineStyle: {
+						color: '#57617B'
+					}
+				}
+			}],
+			series: [{
+				name: '绉诲姩',
+				type: 'line',
+				smooth: true,
+				symbol: 'circle',
+				symbolSize: 5,
+				showSymbol: false,
+				lineStyle: {
+					normal: {
+						width: 1
+					}
+				},
+				areaStyle: {
+					normal: {
+						color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+							offset: 0,
+							color: 'rgba(137, 189, 27, 0.3)'
+						}, {
+							offset: 0.8,
+							color: 'rgba(137, 189, 27, 0)'
+						}], false),
+						shadowColor: 'rgba(0, 0, 0, 0.1)',
+						shadowBlur: 10
+					}
+				},
+				itemStyle: {
+					normal: {
+						color: 'rgb(137,189,27)',
+						borderColor: 'rgba(137,189,2,0.27)',
+						borderWidth: 12
+
+					}
+				},
+				data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
+			}, {
+				name: '鐢典俊',
+				type: 'line',
+				smooth: true,
+				symbol: 'circle',
+				symbolSize: 5,
+				showSymbol: false,
+				lineStyle: {
+					normal: {
+						width: 1
+					}
+				},
+				areaStyle: {
+					normal: {
+						color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+							offset: 0,
+							color: 'rgba(0, 136, 212, 0.3)'
+						}, {
+							offset: 0.8,
+							color: 'rgba(0, 136, 212, 0)'
+						}], false),
+						shadowColor: 'rgba(0, 0, 0, 0.1)',
+						shadowBlur: 10
+					}
+				},
+				itemStyle: {
+					normal: {
+						color: 'rgb(0,136,212)',
+						borderColor: 'rgba(0,136,212,0.2)',
+						borderWidth: 12
+
+					}
+				},
+				data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
+			}, {
+				name: '鑱旈��',
+				type: 'line',
+				smooth: true,
+				symbol: 'circle',
+				symbolSize: 5,
+				showSymbol: false,
+				lineStyle: {
+					normal: {
+						width: 1
+					}
+				},
+				areaStyle: {
+					normal: {
+						color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+							offset: 0,
+							color: 'rgba(219, 50, 51, 0.3)'
+						}, {
+							offset: 0.8,
+							color: 'rgba(219, 50, 51, 0)'
+						}], false),
+						shadowColor: 'rgba(0, 0, 0, 0.1)',
+						shadowBlur: 10
+					}
+				},
+				itemStyle: {
+					normal: {
+
+						color: 'rgb(219,50,51)',
+						borderColor: 'rgba(219,50,51,0.2)',
+						borderWidth: 12
+					}
+				},
+				data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
+			},]
+		};
+
+		return option;
+	}
+
+  let myChart = null;
+
+	function initChart() {
+		if (chart.value) {
+			// myChart = chart.value.getChart();
+
+			let option = getOptions();
+      chart.value.setOption(option);
+		}
+	}
+
+	function updateChart(xLabels, datas) {
+		let option = getOptions(xLabels, datas);
+    // if (myChart) {
+
+    //   myChart.setOption(option);
+    // }
+	}
+
+	onMounted(() => {
+    console.log('line mounted', '=============');
+    
+		initChart();
+	});
+</script>
+
+<template>
+  <div class="line-chart">
+    <base-chart ref="chart"></base-chart>
+  </div>
+</template>
+
+<style scoped>
+.line-chart {
+  width: 100%;
+  height: 100%;
+}
+</style>
diff --git a/src/components/echarts/transparent.js b/src/components/echarts/transparent.js
new file mode 100644
index 0000000..8664f03
--- /dev/null
+++ b/src/components/echarts/transparent.js
@@ -0,0 +1,112 @@
+import * as echarts from "echarts";
+
+let theme = {
+  version: 1,
+  themeName: "transparent",
+  theme: {
+    seriesCnt: 3,
+    backgroundColor: "rgba(0, 0, 0, 0)",
+    titleColor: "#00ffff",
+    subtitleColor: "#00ffff",
+    textColorShow: false,
+    textColor: "#333",
+    markTextColor: "#ffffff",
+    color: [
+      "#22cbbc",
+      "#91cc75",
+      "#fac858",
+      "#ee6666",
+      "#73c0de",
+      "#3ba272",
+      "#fc8452",
+      "#9a60b4",
+      "#ea7ccc",
+    ],
+    borderColor: "#4adfd2",
+    borderWidth: "0",
+    visualMapColor: ["#48dcd4", "#d88273", "#f6efa6"],
+    legendTextColor: "#ffffff",
+    kColor: "#eb5454",
+    kColor0: "#47b262",
+    kBorderColor: "#eb5454",
+    kBorderColor0: "#47b262",
+    kBorderWidth: 1,
+    lineWidth: "2",
+    symbolSize: "8",
+    symbol: "circle",
+    symbolBorderWidth: "0",
+    lineSmooth: true,
+    graphLineWidth: 1,
+    graphLineColor: "#aaa",
+    mapLabelColor: "#000",
+    mapLabelColorE: "rgb(100,0,0)",
+    mapBorderColor: "#444",
+    mapBorderColorE: "#444",
+    mapBorderWidth: 0.5,
+    mapBorderWidthE: 1,
+    mapAreaColor: "#eee",
+    mapAreaColorE: "rgba(255,215,0,0.8)",
+    axes: [
+      {
+        type: "all",
+        name: "閫氱敤鍧愭爣杞�",
+        axisLineShow: true,
+        axisLineColor: "#6E7079",
+        axisTickShow: true,
+        axisTickColor: "#6E7079",
+        axisLabelShow: true,
+        axisLabelColor: "#6E7079",
+        splitLineShow: true,
+        splitLineColor: ["#E0E6F1"],
+        splitAreaShow: false,
+        splitAreaColor: ["rgba(250,250,250,0.2)", "rgba(210,219,238,0.2)"],
+      },
+      {
+        type: "category",
+        name: "绫荤洰鍧愭爣杞�",
+        axisLineShow: true,
+        axisLineColor: "#ffffff",
+        axisTickShow: true,
+        axisTickColor: "#00ffff",
+        axisLabelShow: true,
+        axisLabelColor: "#ffffff",
+        splitLineShow: false,
+        splitLineColor: ["#E0E6F1"],
+        splitAreaShow: true,
+        splitAreaColor: ["rgba(160,158,222,0.2)", "rgba(210,219,238,0.2)"],
+      },
+      {
+        type: "value",
+        name: "鏁板�煎潗鏍囪酱",
+        axisLineShow: true,
+        axisLineColor: "#ffffff",
+        axisTickShow: true,
+        axisTickColor: "#00ffff",
+        axisLabelShow: true,
+        axisLabelColor: "#00ffff",
+        splitLineShow: true,
+        splitLineColor: ["#E0E6F1"],
+        splitAreaShow: false,
+        splitAreaColor: ["rgba(250,250,250,0.2)", "rgba(210,219,238,0.2)"],
+      },
+    ],
+    axisSeperateSetting: true,
+    toolboxColor: "#00ffff",
+    toolboxEmphasisColor: "#ffffff",
+    tooltipAxisColor: "#ffffff",
+    tooltipAxisWidth: 1,
+    timelineLineColor: "#DAE1F5",
+    timelineLineWidth: 2,
+    timelineItemColor: "#A4B1D7",
+    timelineItemColorE: "#FFF",
+    timelineCheckColor: "#316bf3",
+    timelineCheckBorderColor: "fff",
+    timelineItemBorderWidth: 1,
+    timelineControlColor: "#A4B1D7",
+    timelineControlBorderColor: "#A4B1D7",
+    timelineControlBorderWidth: 1,
+    timelineLabelColor: "#A4B1D7",
+  },
+};
+
+echarts.registerTheme("transparent", theme);
\ No newline at end of file
diff --git a/src/components/info.vue b/src/components/info.vue
new file mode 100644
index 0000000..14f102b
--- /dev/null
+++ b/src/components/info.vue
@@ -0,0 +1,54 @@
+<script setup>
+import { ref } from "vue";
+const props = defineProps({
+	label: {
+		type: String,
+		default: "",
+	},
+	value: {
+		type: String,
+		default: "",
+	},
+});
+</script>
+
+<template>
+  <div class="info">
+    <div class="label">{{ label }}</div>
+    <div class="value">{{ value }}</div>
+  </div>
+</template>
+
+<style scoped lang="less">
+.info {
+  display: flex;
+  background: #193B69;
+  height: 32px;
+  font-size: 14px;
+
+  .label {
+    min-width: 6em;
+    color: #fff;
+    background: linear-gradient(#3476BE, #3476BE) left center e('/') 2px 100% no-repeat,
+      linear-gradient(#1E497E, #1E497E) center center e('/') contain no-repeat;
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+      padding-right: 0.6em;
+      &::after {
+        content: ':';
+      }
+  }
+
+  .value {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+    padding-left: 1em;
+    color: #48a5d0;
+    background: linear-gradient(#3476BE, #3476BE) left center e('/') 2px 46% no-repeat,
+        linear-gradient(#377ED2, #377ED2) right bottom e('/') 3px 3px no-repeat;
+  }
+}
+</style>
diff --git a/src/components/siteList/index.vue b/src/components/siteList/index.vue
new file mode 100644
index 0000000..5f06e3a
--- /dev/null
+++ b/src/components/siteList/index.vue
@@ -0,0 +1,311 @@
+<script setup>
+import { ref, reactive, onMounted, computed, onActivated } from "vue";
+import YcTree from "./ycTree.vue";
+import getQueryString from "@/utils/getQueryString";
+import pinyinMatch from 'pinyin-match';
+
+import {
+  getStationTree,
+} from '@/api/station.js';
+
+import getTreeDataByKey from "@/utils/getTreeDataByKey";
+
+const emit = defineEmits(["leaf-click"]);
+const flag = ref(Math.random());
+const treeFlag = ref(0);
+const defaultExpandedKeys = ref([
+  getQueryString("stationId"),
+]);
+
+
+
+const currentNodeKey = ref(getQueryString("battgroupId") || getQueryString("powerId") || "");
+const expanded = ref([]);
+const currentKey = ref('');
+
+const filterText = ref("");
+const treeData = ref([]);
+const siteList = ref([]);
+const siteListCopy = ref([]);
+const treeRef = ref(null);
+
+const getCurrentKey = computed(() => {
+  return currentNodeKey.value ? currentNodeKey.value : currentKey.value;
+});
+
+const expandedKeys = computed(() => {
+  // let parentKey = defaultExpandedKeys.value.join('-');
+  let parentKey = defaultExpandedKeys.value[0];
+  if (parentKey) {
+    return [parentKey];
+  } else {
+    let _expanded = expanded.value;
+    return _expanded;
+  }
+});
+
+function filterMethod(val) {
+  if (val) {
+    siteList.value = siteListCopy.value.filter((item) => {
+      return pinyinMatch.match(item, val);
+    });
+  } else {
+    siteList.value = siteListCopy.value;
+  }
+}
+
+const hideFlag = ref(false);
+
+function toggleChange() {
+  // emit("toggleChange");
+  hideFlag.value = !hideFlag.value;
+}
+
+function filterChange(val) {
+  if (val) {
+    let homeInfo = siteListCopy.value.filter(v => v.id == val);
+
+    if (homeInfo.length) {
+      let item = homeInfo[0];
+      defaultExpandedKeys.value = [item.id];
+
+      let expandedNode = getTreeDataByKey(expandedKeys.value[0], treeData.value, 'key');
+      if (expandedNode != -1) {
+        expanded.value = [expandedNode.key];
+        currentNodeKey.value = expandedNode.children[0].isleaf ? expandedNode.children[0].key : expandedNode.children[0].children[0].key;
+        leafClick(expandedNode.children[0].isleaf ? expandedNode.children[0] : expandedNode.children[0].children[0]);
+      }
+    }
+  }
+}
+
+function nodeClick(data) {
+  // console.log('nodeClick', data, '=============');
+  
+}
+
+function leafClick(data) {
+  console.log('leafClick', data, '=============');
+  emit('leaf-click', data);
+}
+
+async function getSiteList() {
+  let { code, data, data2 } = await getStationTree();
+  let list = [];
+  if (code && data) {
+    list = data2;
+  }
+  formatData(list);
+  
+}
+
+function formatData(list) {
+  let sites = [];
+  list.forEach(v => {
+    let pro = v.provice;
+    let citys = v.cityList;
+    v.label = pro;
+    v.key = pro;
+    v.children = citys;
+    citys.forEach(v1 => {
+      let city = v1.city;
+      let countries = v1.countryList;
+      v1.label = city;
+      v1.key = city;
+      v1.children = countries;
+
+      countries.forEach(v2 => {
+        let country = v2.country;
+        let _list = v2.stationList;
+        v2.label = country;
+        v2.key = country;
+        v2.children = _list;
+        _list.forEach(v3 => {
+          v3.label = v3.stationName;
+          v3.key = v3.stationId;
+          let powerList = v3.pinflist;
+          v3.children = powerList;
+          sites.push({
+            label: `${pro}-${city}-${country}-${v3.stationName}`,
+            id: v3.stationId,
+            data: {
+              provice: pro,
+              city: city,
+              country: country,
+              stationName: v3.stationName,
+              children: powerList
+            }
+          });
+          powerList.forEach(v4 => {
+            v4.label = v4.powerName;
+            v4.key = `${v4.stationId}-${v4.powerId}`;
+            v4.children = v4.battList;
+            v4.isleaf = !v4.battList.length;
+
+            v4.battList.forEach(v5 => {
+              v5.label = `${v5.devName}-${v5.battgroupName}`;
+              v5.key = `${v5.stationId}-${v5.powerId}-${v5.devId}-${v5.battgroupId}`;
+              v5.isleaf = true;
+            });
+          });
+        });
+      });
+    });
+  });
+
+  siteListCopy.value = sites.map(v => v);
+  treeData.value = list;
+  if (sites.length) {
+    let expandedNode = getTreeDataByKey(sites[0].id, treeData.value, 'key');
+    if (expandedNode != -1) {
+      expanded.value = [expandedNode.key];
+      let selectKey = expandedNode.children[0].isleaf ? expandedNode.children[0].key : expandedNode.children[0].children[0].key;
+      currentNodeKey.value = selectKey;
+      let res = getTreeDataByKey(selectKey, treeData.value, 'key');
+
+      leafClick(res);
+      // leafClick(expandedNode.children[0].isleaf ? expandedNode.children[0] : expandedNode.children[0].children[0]);
+    }
+  }
+}
+
+function updateSelect() {
+  let stationId = getQueryString("stationId");
+  let powerId = getQueryString("powerId");
+  let battgroupId = getQueryString("battgroupId");
+  let devId = getQueryString("devId");
+  if (stationId) {
+    let expandedNode = getTreeDataByKey(stationId, treeData.value, 'key');
+    console.log('expandedNode', expandedNode, '=============');
+    
+    if (expandedNode != -1) {
+      expanded.value = [expandedNode.key];
+      // 鏍规嵁涓嶅悓鎯呭喌閫変腑瑕佹墦寮�鐨勫彾瀛愯妭鐐�
+      // 濡傛灉鍙湁stationId  鍒欐墦寮�stationId鐨勭涓�涓猵owerId 鎴栦笅闈㈢殑 鐨勭涓�涓猙attgroupId
+      // 濡傛灉鏈塸owerId 1,鐢垫簮鏄彾瀛愯妭鐐�  鍒欐墦寮�杩欎釜瀵瑰簲鐨勭數婧�  2,鐢垫簮涓嶆槸鍙跺瓙鑺傜偣  a,濡傛灉鍙湁璁惧id 鍒欐墦寮�鐢垫簮childern鐨勭涓�涓瓑浜庡垯璁惧id鐨勭數姹犵粍  b,濡傛灉鏈塨attgroupId  鍒欐墦寮�杩欎釜瀵瑰簲鐨勭數姹犵粍 c,濡傛灉鏈塪evId  鍒欐墦寮�鐢垫簮涓嬬涓�涓數姹犵粍
+      let selectKey = '';
+      if (powerId) {
+        // 鐢垫簮鑺傜偣
+        let powerNode = getTreeDataByKey(`${stationId}-${powerId}`, treeData.value, 'key');
+        if (powerNode.isleaf) {
+          selectKey = powerNode.key;
+        } else {
+          if (battgroupId) {
+            selectKey = `${stationId}-${powerId}-${devId}-${battgroupId}`;
+          } else if (devId) {
+            selectKey = powerNode.children.filter(v => v.devId == devId)[0].key;
+          } else {
+            selectKey = powerNode.children[0].key;
+          }
+        }
+      } else {
+        selectKey = expandedNode.children[0].isleaf ? expandedNode.children[0].key : expandedNode.children[0].children[0].key;
+      }
+      currentNodeKey.value = selectKey;
+
+      // console.log('currentKey', currentKey.value, 'expanded', expanded.value, 'getCurrentKey', getCurrentKey.value, '=============');
+      treeFlag.value = Math.random();
+      let res = getTreeDataByKey(selectKey, treeData.value, 'key');
+      // console.log('res', res, '=============');
+      
+      
+      leafClick(res);
+    }
+  }
+}
+
+onActivated(async () => {
+  if (!treeData.value.length) {
+    await getSiteList();
+  }
+  let pageFlag = getQueryString("pageFlag");
+  console.log('pageFlag', pageFlag, flag.value, '=============');
+  
+  if (pageFlag &&pageFlag != flag.value) {
+    updateSelect();
+    flag.value = pageFlag;
+  }
+});
+
+
+onMounted(() => {
+  getSiteList();
+});
+
+</script>
+
+<template>
+  <card
+    title="绔欑偣鍒楄〃"
+    :class="['site-list', { 'hide': hideFlag }]"
+  >
+    <flex-layout>
+      <template v-slot:header>
+        <el-select
+          size="small"
+          v-model="filterText"
+          class="search"
+          filterable
+          clearable
+          :filter-method="filterMethod"
+          placeholder="璇烽�夋嫨"
+          @change="filterChange"
+        >
+          <el-option
+            v-for="(item, idx) in siteList"
+            :key="'site_' + idx"
+            :label="item.label"
+            :value="item.id"
+          />
+        </el-select>
+      </template>
+      <yc-tree
+        ref="treeRef"
+        :data="treeData"
+        :key="treeFlag"
+        :default-expand-keys="expandedKeys"
+        :current-node-key="getCurrentKey"
+        @node-click="nodeClick"
+        @leaf-click="leafClick"
+      ></yc-tree>
+    </flex-layout>
+    <div class="btn" @click="toggleChange">
+      <svg-icon icon-class="arrow"></svg-icon>
+    </div>
+  </card>
+</template>
+
+<style scoped lang="less">
+.site-list {
+  width: 320px;
+  position: relative;
+  transition: all 0.5s ease;
+  .btn {
+    display: flex;
+    position: absolute;
+    right: 0;
+    top: 50%;
+    width: 30px;
+    height: 80px;
+    background: rgba(0, 0, 0, 0.5);
+    cursor: pointer;
+    transform: translateY(-50%) rotate(180deg);
+    transform-origin: center center;
+    transition: all 0.5s ease;
+    align-items: center;
+    justify-content: center;
+
+  }
+  &.hide {
+    transform: translate(-100%, 0);
+    margin-right: -320px;
+    .btn {
+      right: -8px;
+      transform: translate(100%, -50%) rotate(0deg);
+    }
+  }
+  :deep(.flex-layout-header) {
+    padding: 6px 10px;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/components/siteList/ycTree.vue b/src/components/siteList/ycTree.vue
new file mode 100644
index 0000000..3bb65fb
--- /dev/null
+++ b/src/components/siteList/ycTree.vue
@@ -0,0 +1,116 @@
+<script setup>
+import { ref, reactive, watch, onMounted, nextTick } from "vue";
+const props = defineProps({
+	data: {
+		type: Array,
+		default() {
+			return [];
+		}
+	},
+	defaultExpandKeys: {
+		type: Array,
+		default() {
+			return []
+		}
+	},
+	currentNodeKey: {
+		type: [String, Number],
+		default: ""
+	},
+});
+
+const tree = ref(null);
+const current = ref('');
+const defaultProps = reactive({
+  children: 'children',
+  label: 'label',
+  isLeaf: 'isLeaf',
+});
+
+watch(
+  () => props.currentNodeKey,
+  (val) => {
+    nextTick(() => {
+      current.value = val;
+      tree.value.setCurrentKey(val);
+    });
+  }
+);
+
+const emit = defineEmits(['node-click', 'leaf-click']);
+
+function nodeClick(data, node) {
+  node.isCurrent = node.isLeaf;
+  if (node.isLeaf && current.value != node.key) {
+    current.value = node.key;
+    emit('leaf-click', data, node);
+  } else {
+    emit('node-click', data, node);
+  }
+  tree.value.setCurrentKey(current.value);
+}
+
+function nodeExpand(data, node) {
+  node.isCurrent = node.isLeaf;
+  if (!node.isLeaf && current.value != node.key) {
+    emit('node-click', data, node);
+  }
+}
+
+function nodeCollapse(data, node) {
+  loopClose(node.childrenNodes);
+}
+
+function loopClose(childNodes) {
+  if(!childNodes || childNodes.length === 0) {
+    return;
+  }
+  childNodes.map(item=>{
+    item.expanded = false;
+    loopClose(item.childNodes);
+  });
+}
+
+
+onMounted(() => {
+  setTimeout(() => {
+    if(tree.value) {
+      tree.value.setCurrentKey(props.currentNodeKey);
+      current.value = props.currentNodeKey;
+    }
+  }, 500);
+});
+
+
+</script>
+
+<template>
+  <el-tree
+    class="filter-tree"
+    :props="defaultProps"
+    :auto-expand-parent="true"
+    node-key="key"
+    ref="tree"
+    :data="data"
+    :default-expanded-keys="defaultExpandKeys"
+    :current-node-key="currentNodeKey"
+    @node-collapse="nodeCollapse"
+    @node-click="nodeClick"
+    @node-expand="nodeExpand"
+  >
+    <template #default="{ node }">
+      <span class="span-ellipsis">
+        <span :title="node.label">{{ node.label }}</span>
+      </span>
+    </template>
+  </el-tree>
+</template>
+
+<style scoped lang="less">
+.filter-tree {
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+</style>
diff --git a/src/components/svg/dotLine.vue b/src/components/svg/dotLine.vue
new file mode 100644
index 0000000..40d3d01
--- /dev/null
+++ b/src/components/svg/dotLine.vue
@@ -0,0 +1,53 @@
+<script setup>
+import { ref } from "vue";
+const props = defineProps({
+  offset: {
+      type: Array,
+      default() {
+        return [0, 0];
+      },
+    },
+    points: {
+      type: Array,
+      default() {
+        return [
+          [0, 0],
+          [10, 10],
+        ];
+      },
+    },
+    color: {
+      type: String,
+      default: "#804000",
+    },
+    dotIndexs: {
+      type: Array,
+      default() {
+        return [0]
+      }
+    },
+    dotR: {
+      type: [Number, String],
+      default: 4
+    }
+});
+
+</script>
+
+<template>
+<g
+    ref="g"
+    :transform="'translate(' + offset.join(',') + ')'"
+  >
+    <polyline
+      :points="points.join(' ')"
+      stroke-dasharray="4,2"
+      :stroke="color"
+      style="fill: none; stroke-width: 2"
+    />
+  </g>
+</template>
+
+<style scoped lang="less">
+
+</style>
\ No newline at end of file
diff --git a/src/components/svg/stationSvg.vue b/src/components/svg/stationSvg.vue
new file mode 100644
index 0000000..71aff37
--- /dev/null
+++ b/src/components/svg/stationSvg.vue
@@ -0,0 +1,35 @@
+<script>
+import rightPublic from "@/components/svg/rightPublic/index.vue";
+import rightSwitch from "@/components/svg/rightSwitch/index.vue";
+import tenToTwelve from "@/components/svg/tenToTwelve/index.vue";
+import sevenToNine from "@/components/svg/sevenToNine/index.vue";
+export default {
+  name: "stationSvg",
+  components: {rightPublic, rightSwitch, tenToTwelve, sevenToNine},
+  props: {
+    type: {
+      type: Number,
+      default: 1
+    }
+  },
+  data() {
+    return {};
+  },
+  methods: {},
+  mounted() {
+
+  }
+}
+</script>
+
+<template>
+  <right-switch v-if="type === 4"></right-switch>
+  <right-public v-else-if="type === 3"></right-public>
+  <ten-to-twelve v-else-if="type === 2"></ten-to-twelve>
+  <seven-to-nine v-else-if="1"></seven-to-nine>
+  <div v-else>鏈煡鎷撴墤鍥撅紝鑱旂郴绠$悊鍛樻柊澧烇紒锛侊紒</div>
+</template>
+
+<style scoped lang="less">
+
+</style>
diff --git a/src/components/svg/svgArc.vue b/src/components/svg/svgArc.vue
new file mode 100644
index 0000000..1d7767a
--- /dev/null
+++ b/src/components/svg/svgArc.vue
@@ -0,0 +1,40 @@
+<script setup name="SvgArc">
+  import { ref, computed } from "vue";
+  const props = defineProps({
+    offset: {
+      type: Array,
+      default() {
+        return [0, 0];
+      },
+    },
+    isVertical: {
+      type: Boolean,
+      default: false
+    },
+    r: {
+      type: [Number, String],
+      default: 8,
+    },
+    color: {
+      type: String,
+      default: "#4cb5e2",
+    },
+    lineWidth: {
+      type: [Number, String],
+      default: 2,
+    },
+  });
+
+  const path = computed(() => {
+    let r = props.r;
+    let end = props.isVertical ? `0,${r*2}` : `${r*2},0`;
+    let path = `M 0,0 A ${r},${r} 0 1 1 ${end}`;
+    return path;
+  });
+</script>
+
+<template>
+	<g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+		<path :d="path" :stroke="color" :stroke-width="lineWidth" fill="none" />
+	</g>
+</template>
diff --git a/src/components/svg/svgCabinet.vue b/src/components/svg/svgCabinet.vue
new file mode 100644
index 0000000..4526f78
--- /dev/null
+++ b/src/components/svg/svgCabinet.vue
@@ -0,0 +1,258 @@
+<script setup>
+import { ref, computed, onMounted } from "vue";
+import svgLock from "./svgLock.vue";
+const emit = defineEmits(["showInfo"]);
+
+const props = defineProps({
+	offset: {
+		type: Array,
+		default: [0, 0],
+	},
+	width: {
+		type: [Number, String],
+		default: 80,
+	},
+	lineWidth: {
+		type: [Number, String],
+		default: 2,
+	},
+	color: {
+		type: String,
+		default: "#804000",
+	},
+  offColor: {
+    type: String,
+    // default: '#888',
+    default: '#f00',
+  },
+  onColor: {
+    type: String,
+    default: '#0f0',
+  },
+  disabled: {
+    type: Boolean,
+    default: false,
+  },
+  onLine0: {
+    type: [Number, String],
+  },
+  onLine1: {
+    type: [Number, String],
+  },
+  lock0: {
+    type: Boolean,
+    default: false,
+  },
+  lock1: {
+    type: Boolean,
+    default: false,
+  },
+});
+
+/**
+ *      0 ------- 1
+      /|        /|
+     / |       / |
+    3 ------- 2  |
+    |  |      |  |
+    |  4 ---- |-- 5
+    | /       | /
+    |/        |/
+    7 ------- 6
+ */
+const x0 = computed(() => {
+	return props.width / 2 - props.width / 6;
+});
+
+const x1 = computed(() => {
+	return props.width;
+});
+
+const x2 = computed(() => {
+  return props.width / 2 + props.width / 6;
+});
+
+const x3 = computed(() => {
+  return 0;
+});
+
+const y0 = computed(() => {
+	return 0;
+});
+
+const y1 = computed(() => {
+	return props.width * 2 / 4;
+});
+
+const y2 = computed(() => {
+	return props.width * 2 * 3 / 4;
+});
+
+const y3 = computed(() => {
+  return props.width * 2;
+});
+
+const y4 = computed(() => {
+  return props.width * 2 * 5 / 8;
+});
+
+const y5 = computed(() => {
+  return props.width * 2 * 3 / 8;
+});
+
+const strokeColor = computed(() => {
+  return props.disabled ? 'rgba(125,125,125,0.2)' : props.color;
+});
+
+const statusColor0 = computed(() => {
+  if (props.disabled || !props.onLine0) return '#888';
+  return props.lock0 ? props.onColor : props.offColor;
+});
+
+const statusColor1 = computed(() => {
+  if (props.disabled || !props.onLine1) return '#888';
+  return props.lock1 ? props.onColor : props.offColor;
+});
+
+const onLineColor0 = computed(() => {
+  if (props.disabled) return '#888';
+  switch(props.onLine0) {
+    case 1:
+      return props.onColor;
+    case 0:
+      return props.offColor;
+    default:
+      return '#888';
+  }
+});
+
+const onLineColor1 = computed(() => {
+  if (props.disabled) return '#888';
+  
+  switch(props.onLine1) {
+    case 1:
+      return props.onColor;
+    case 0:
+      return props.offColor;
+    default:
+      return '#888';
+  }
+});
+
+function showInfo(idx, event) {
+  if (props.disabled) return;
+  emit("showInfo", { idx, event });
+}
+
+
+
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <!-- 0-1 -->
+    <line :x1="x0" :x2="x1" :y1="y0" :y2="y0" :stroke="strokeColor" :stroke-width="lineWidth" />
+    <!-- 0-3 -->
+    <line
+      :x1="x0"
+      :y1="y0"
+      :x2="x3"
+      :y2="y1"
+      :stroke="strokeColor"
+      :stroke-width="lineWidth"
+    />
+    <!-- 3-2 -->
+    <line
+      :x1="x3"
+      :y1="y1"
+      :x2="x2"
+      :y2="y1"
+      :stroke="strokeColor"
+      :stroke-width="lineWidth"
+    />
+    <!-- 2-1 -->
+    <line
+      :x1="x2"
+      :y1="y1"
+      :x2="x1"
+      :y2="y0"
+      :stroke="strokeColor"
+      :stroke-width="lineWidth"
+    />
+    <!-- 3-7 -->
+    <line :x1="x3" :x2="x3" :y1="y1" :y2="y3" :stroke="strokeColor" :stroke-width="lineWidth" />
+    <!-- 7-6 -->
+    <line
+      :x1="x3"
+      :y1="y3"
+      :x2="x2"
+      :y2="y3"
+      :stroke="strokeColor"
+      :stroke-width="lineWidth"
+    />
+    <!-- 2-6 -->
+    <line
+      :x1="x2"
+      :y1="y1"
+      :x2="x2"
+      :y2="y3"
+      :stroke="strokeColor"
+      :stroke-width="lineWidth"
+    />
+    <!-- 5-6 -->
+    <line
+      :x1="x1"
+      :y1="y2"
+      :x2="x2"
+      :y2="y3"
+      :stroke="strokeColor"
+      :stroke-width="lineWidth"
+    />
+    <!-- 1-5 -->
+    <line :x1="x1" :x2="x1" :y1="y0" :y2="y2" :stroke="strokeColor" :stroke-width="lineWidth" />
+    <!-- 0-4 -->
+    <line
+      :x1="x0"
+      :y1="y0"
+      :x2="x0"
+      :y2="y2"
+      stroke-dasharray="4,2"
+      :stroke="strokeColor"
+      :stroke-width="lineWidth"
+    />
+    <!-- 4-5 -->
+    <line
+      :x1="x0"
+      :y1="y2"
+      :x2="x1"
+      :y2="y2"
+      stroke-dasharray="4,2"
+      :stroke="strokeColor"
+      :stroke-width="lineWidth"
+    />
+    <!-- 4-7 -->
+    <line
+      :x1="x0"
+      :y1="y2"
+      :x2="x3"
+      :y2="y3"
+      stroke-dasharray="4,2"
+      :stroke="strokeColor"
+      :stroke-width="lineWidth"
+    />
+    <!-- 鍓嶉棬閿� -->
+    <svg-lock :offset="[x3 + 6, y4 - 14]"
+      :lockFillColor="statusColor0"
+      :stateColor="onLineColor0"
+      @mouseenter="showInfo(0, $event)"
+    ></svg-lock>
+    <!-- 鍚庨棬閿� -->
+    <svg-lock :offset="[x1 - 14, y5 - 10]"
+      :lockFillColor="statusColor1"
+      :stateColor="onLineColor1"
+      @mouseenter="showInfo(1, $event)"
+    ></svg-lock>
+  </g>
+</template>
+
+<style scoped lang="less"></style>
diff --git a/src/components/svg/svgDiode.vue b/src/components/svg/svgDiode.vue
new file mode 100644
index 0000000..efc4b84
--- /dev/null
+++ b/src/components/svg/svgDiode.vue
@@ -0,0 +1,78 @@
+<script setup name="SvgDiode">
+import { ref, computed } from "vue";
+const props = defineProps({
+  len: {
+    type: Number,
+    default: 20,
+  },
+  offset: {
+    type: Array,
+    default() {
+      return [0, 0];
+    },
+  },
+  color: {
+    type: String,
+    default: "#f59a23",
+  },
+  lineWidth: {
+    type: [Number, String],
+    default: 2,
+  },
+  // 鏂瑰悜涓轰簩鏋佺瀵奸�氭柟鍚� 涓夎褰㈡寚鍚�
+  direct: {
+    type: String,
+    default: "left",
+    validator: (v) => ["left", "right", "top", "bottom"].includes(v),
+  },
+});
+
+const y1 = computed(() => {
+  let len = props.len;
+  return len / -2;
+});
+
+const y2 = computed(() => {
+  let len = props.len;
+  return len / 2;
+});
+
+const points = computed(() => {
+  let len = props.len;
+  let lineWidth = props.lineWidth;
+  let len2 = len / 2;
+  return [
+    [lineWidth, 0],
+    [lineWidth + Math.cos((30 * Math.PI) / 180) * len, len2],
+    [lineWidth + Math.cos((30 * Math.PI) / 180) * len, -len2],
+  ].join(" ");
+});
+
+const transform = computed(() => {
+  let { offset, direct, lineWidth, len } = props;
+  let rotate = "";
+  switch (direct) {
+    case "left":
+      rotate = "";
+      break;
+    case "right":
+      rotate = `rotate(180 0 0) translate(-${lineWidth + Math.cos((30 * Math.PI) / 180) * len} 0)`;
+      break;
+    case "top":
+      rotate = "rotate(90 0 0)";
+      break;
+    case "bottom":
+      rotate = `rotate(270 0 0) translate(-${lineWidth + Math.cos((30 * Math.PI) / 180) * len} 0)`;
+      break;
+  }
+  return `translate(${offset.join(",")}) ${rotate}`;
+});
+
+</script>
+
+<template>
+  <g :transform="transform">
+    <polygon :points="points" stroke="none" :fill="color" />
+    <line :y1="y1" :y2="y2" :stroke-width="lineWidth" :stroke="color"></line>
+  </g>
+</template>
\ No newline at end of file
diff --git a/src/components/svg/svgDot.vue b/src/components/svg/svgDot.vue
new file mode 100644
index 0000000..55daa65
--- /dev/null
+++ b/src/components/svg/svgDot.vue
@@ -0,0 +1,25 @@
+<script setup>
+import { ref } from "vue";
+const props = defineProps({
+	offset: {
+		type: Array,
+		default: [0, 0],
+	},
+	r: {
+		type: [Number, String],
+		default: 6,
+	},
+	color: {
+		type: String,
+		default: "#804000",
+	},
+});
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <circle :r="r" :fill="color" />
+  </g>
+</template>
+
+<style scoped lang="less"></style>
diff --git a/src/components/svg/svgDotRect.vue b/src/components/svg/svgDotRect.vue
new file mode 100644
index 0000000..ef4485e
--- /dev/null
+++ b/src/components/svg/svgDotRect.vue
@@ -0,0 +1,66 @@
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <rect
+      :width="width"
+      :height="height"
+      :stroke="color"
+      :fill="fillColor"
+      :stroke-width='strokeWidth'
+      :stroke-dasharray='strokeDasharray'
+      :stroke-dashoffset='strokeDashoffset'
+      :rx="rx"
+      :ry="ry"
+    />
+  </g>
+</template>
+
+<script setup name="SvgDotRect">
+import { ref } from "vue";
+const props = defineProps({
+  offset: {
+    type: Array,
+    default() {
+      return [0, 0];
+    },
+  },
+  width: {
+    type: [Number, String],
+    default: 4,
+  },
+  height: {
+    type: [Number, String],
+    default: 4,
+  },
+  color: {
+    type: String,
+    default: "#0ff",
+  },
+  fillColor: {
+    type: String,
+    // default: 'transparent',
+    default: '#00000066',
+  },
+  rx: {
+    type: [Number, String],
+    default: 0,
+  },
+  ry: {
+    type: [Number, String],
+    default: 0,
+  },
+  strokeWidth: {
+    type: [Number, String],
+    default: 0,
+  },
+  strokeDasharray: {
+    type: String,
+    default: '8 4',
+  },
+  strokeDashoffset: {
+    type: [Number, String],
+    default: 0,
+  },
+});
+</script>
+
+<style scoped></style>
diff --git a/src/components/svg/svgFloor.vue b/src/components/svg/svgFloor.vue
new file mode 100644
index 0000000..e2271b4
--- /dev/null
+++ b/src/components/svg/svgFloor.vue
@@ -0,0 +1,45 @@
+<script setup>
+import { ref } from "vue";
+const props = defineProps({
+  offset: {
+      type: Array,
+      default() {
+        return [0, 0];
+      },
+    },
+    points: {
+      type: Array,
+      default() {
+        return [
+        ];
+      },
+    },
+    color: {
+      type: String,
+      default: 'rgba(0, 255, 255, 0.2)',
+    },
+    fillColor: {
+      type: String,
+      default: "#032134",
+    }
+});
+
+</script>
+
+<template>
+<g
+    ref="g"
+    :transform="'translate(' + offset.join(',') + ')'"
+  >
+    <polygon
+      :points="points.join(' ')"
+      :stroke="color"
+      :fill="fillColor"
+      style="stroke-width: 2"
+    />
+  </g>
+</template>
+
+<style scoped lang="less">
+
+</style>
\ No newline at end of file
diff --git a/src/components/svg/svgFlow.vue b/src/components/svg/svgFlow.vue
new file mode 100644
index 0000000..2c77f90
--- /dev/null
+++ b/src/components/svg/svgFlow.vue
@@ -0,0 +1,133 @@
+<script setup>
+import { ref, computed } from "vue";
+
+const props = defineProps({
+	offset: {
+		type: Array,
+		default: () => [0, 0],
+	},
+  /**
+   * 姣忎竴娈佃矾寰勪竴涓璞�
+   * {
+   *   points: [],
+   *   鐐逛箣鍓嶆槸鍚﹂渶瑕佸渾瑙�  涓暟姣攑oints灏戜袱涓� 鍦嗚涓簍rue
+   *   roundCornerFlags: [],
+   * }
+   */
+	lineList: {
+		type: Array,
+		default: () => [],
+	},
+	color: {
+		type: String,
+		// default: "#67aa57",
+		default: "#fff",
+	},
+	lineWidth: {
+		type: Number,
+		default: 16,
+	},
+  r: {
+    type: [Number, String],
+    default: 6
+  },
+});
+
+function calcVectorProperties(A, B) {
+  let [x0, y0] = A;
+  let [x1, y1] = B;
+  // 璁$畻鍚戦噺鍒嗛噺
+  const dx = x1 - x0;
+  const dy = y1 - y0;
+  
+  // 璁$畻鍚戦噺鐨勬ā闀匡紙娆у嚑閲屽緱璺濈锛�
+  const magnitude = Math.sqrt(dx * dx + dy * dy);
+  if (magnitude < props.r) {
+    throw new Error('涓ょ偣涔嬮棿璺濈杩囪繎');
+  }
+  
+  // 澶勭悊闆跺悜閲忔儏鍐碉紙閬垮厤闄や互闆讹級
+  const normalizedVector = magnitude === 0 
+      ? [0, 0]
+      : [ dx / magnitude, dy / magnitude ];
+  
+  // 璁$畻鍚戦噺涓巟杞存鏂瑰悜鐨勫す瑙掞紙寮у害锛�
+  // Math.atan2 杩斿洖鍊艰寖鍥达細[-蟺, 蟺]
+  const angleRadians = Math.atan2(normalizedVector[1], normalizedVector[0]);
+
+  // AB涓婄B鐐圭殑璺濈r璺濈鐨勭偣鐨勫潗鏍囷紙x2, y2锛�
+  const x2 = x1 - props.r * normalizedVector[0];
+  const y2 = y1 - props.r * normalizedVector[1];
+  
+  return {
+    x2,
+    y2
+  };
+}
+
+const pathList = computed(() => {
+  let list = props.lineList;
+  let res = [];
+  for (let i = 0, len = list.length; i < len; i++) {
+    let { points, roundCornerFlags, lineColor } = list[i];
+    let path = `M ${points[0].join(",")} `;
+    for (let j = 1, len2 = points.length; j < len2; j++) {
+      let str = '';
+      let p0 = points[j - 1],
+      p1 = points[j],
+      p2 = points[j + 1];
+
+      if (j < len2 - 1 &&  roundCornerFlags[j - 1]) {
+        let { x2, y2 } = calcVectorProperties(p0, p1);
+        let { x2: x3, y2: y3 } = calcVectorProperties(p2, p1);
+        str = ` L ${x2},${y2} Q ${p1.join(",")} ${x3},${y3} `;
+      } else {
+        str = ` L ${points[j].join(",")} `
+      }
+      path += str;
+    }
+    res.push({path, lineColor});
+  }
+  return res;
+});
+
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <path v-for="(item, index) in pathList" :key="'path0_' + index"
+      fill="none"
+      :d="item['path']"
+      :stroke-width="2"
+      :stroke="item['lineColor']"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+    >
+      <animate attributeName="stroke-width" values="2;8;2" dur="2s" repeatCount="indefinite" />
+      <animate attributeName="opacity" values="1;0.4;1" dur="2s" repeatCount="indefinite" />
+    </path>
+    <path v-for="(item, index) in pathList" :key="'path1_' + index"
+      fill="none"
+      :d="item['path']"
+      :stroke-width="lineWidth - 4"
+      stroke="rgba(255,255,255, 0.4)"
+      stroke-linecap="round"
+      stroke-linejoin="round"
+        stroke-dasharray="18 142"
+        stroke-dashoffset="174"
+    >
+      <animate attributeName="stroke-dashoffset" from="174" to="14" dur="4s" repeatCount="indefinite" />
+      <animate attributeName="stroke-width" :values="`${lineWidth - 8};${lineWidth + 4};${lineWidth - 8}`" dur="2s" repeatCount="indefinite" />
+    </path>
+    <path v-for="(item, index) in pathList" :key="'path2_' + index"
+       :d="item['path']" :stroke="color" :stroke-width="lineWidth"
+        fill="none"
+        stroke-linecap="round"
+        stroke-linejoin="round"
+        stroke-dasharray="4 156"
+        stroke-dashoffset="160">
+      <animate attributeName="stroke-dashoffset" from="160" to="0" dur="4s" repeatCount="indefinite" />
+      <animate attributeName="stroke-width" :values="`${lineWidth - 8};${lineWidth + 8};${lineWidth - 8}`" dur="2s" repeatCount="indefinite" />
+    </path>
+  </g>
+</template>
\ No newline at end of file
diff --git a/src/components/svg/svgImg.vue b/src/components/svg/svgImg.vue
new file mode 100644
index 0000000..17fca05
--- /dev/null
+++ b/src/components/svg/svgImg.vue
@@ -0,0 +1,38 @@
+<script setup>
+import { ref,  } from "vue";
+const props = defineProps({
+  img: {
+    type: String,
+    default: "",
+  },
+  offset: {
+    type: Array,
+    default() {
+      return [0, 0];
+    },
+  },
+  width: {
+    type: Number,
+    default: 40,
+  },
+  height: {
+    type: Number,
+    default: 160,
+  },
+})
+
+</script>
+
+<template>
+  <g :transform="'translate(' + offset.join(',') + ')'">
+    <image
+      :width="width"
+      :height="height"
+      :xlink:href="img"
+    />
+  </g>
+</template>
+
+<style scoped lang="less">
+
+</style>
\ No newline at end of file
diff --git a/src/components/svg/svgInputBorderBox.vue b/src/components/svg/svgInputBorderBox.vue
new file mode 100644
index 0000000..3bcb6b7
--- /dev/null
+++ b/src/components/svg/svgInputBorderBox.vue
@@ -0,0 +1,124 @@
+<script setup>
+import { ref, computed } from "vue";
+
+const props = defineProps({
+	offset: {
+		type: Array,
+		default() {
+			return [0, 0];
+		},
+	},
+  borderWidth: {
+    type: [Number, String],
+    default: 6,
+  },
+	width: {
+		type: [Number, String],
+		default: 142,
+	},
+	height: {
+		type: [Number, String],
+		default: 72,
+	},
+	color: {
+		type: String,
+		default: "url(#colorBorder0)",
+	},
+	fillColor: {
+		type: String,
+		default: '#989898',
+	},
+	tColor: {
+		type: String,
+		default: "#fff",
+	},
+	text: {
+		type: [Number, String],
+		default: "Text",
+	},
+  inputValue: {
+    type: [Number, String],
+    default: 0,
+  },
+	fontSize: {
+		type: [Number, String],
+		default: 16,
+	},
+	textAnchor: {
+		type: String,
+		validator: (v) => ["start", "middle", "end"].includes(v),
+		default: "middle",
+	},
+  unit: {
+    type: String,
+    default: "",
+  },
+});
+
+const _width = computed(() => {
+  return props.width - 2 * props.borderWidth;
+});
+
+const _height = computed(() => {
+  return props.height - 2 * props.borderWidth;
+});
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <rect
+      :width="width"
+      :height="height"
+      :stroke="color"
+      :fill="fillColor"
+      style="stroke-width: 1"
+    />
+    <rect
+      :x="borderWidth"
+      :y="borderWidth"
+      :width="_width"
+      :height="_height"
+      stroke="url(#colorBorder0_reverse)"
+      :fill="fillColor"
+      style="stroke-width: 1"
+    />
+    <text
+      :x="width / 2"
+      :y="height / 2"
+      :dy="fontSize * -0.5"
+      :fill="tColor"
+      :text-anchor="textAnchor"
+      :font-size="fontSize"
+      >{{ text }}</text
+    >
+    <rect
+      :x="width / 2 - 44"
+      :y="height / 2"
+      :width="58"
+      :height="22"
+      stroke="none"
+      fill="#000"
+    />
+    <text
+      :x="width / 2 - 15"
+      :y="height / 2 + 11"
+      :dy="fontSize / 2.6"
+      fill="#0f0"
+      text-anchor="middle"
+      :font-size="fontSize"
+      >{{ inputValue }}</text
+    >
+    <text
+      v-if="unit"
+      :x="width / 2 +24"
+      :y="height / 2 + 11"
+      :dy="fontSize / 2.6"
+      :fill="tColor"
+      text-anchor="start"
+      :font-size="fontSize"
+      >{{ unit }}</text
+    >
+  </g>
+</template>
+
+<style scoped lang="less"></style>
diff --git a/src/components/svg/svgInputBox.vue b/src/components/svg/svgInputBox.vue
new file mode 100644
index 0000000..c6556af
--- /dev/null
+++ b/src/components/svg/svgInputBox.vue
@@ -0,0 +1,82 @@
+<script setup>
+import { ref } from "vue";
+
+const props = defineProps({
+	offset: {
+		type: Array,
+		default() {
+			return [0, 0];
+		},
+	},
+	width: {
+		type: [Number, String],
+		default: 58,
+	},
+	height: {
+		type: [Number, String],
+		default: 22,
+	},
+	color: {
+		type: String,
+		default: "#aaa",
+	},
+	fillColor: {
+		type: String,
+		default: 'transparent',
+	},
+	tColor: {
+		type: String,
+		default: "#0e0",
+	},
+	text: {
+		type: [Number, String],
+		default: "Text",
+	},
+  unit: {
+    type: String,
+    default: "",
+  },
+	fontSize: {
+		type: [Number, String],
+		default: 16,
+	},
+	textAnchor: {
+		type: String,
+		validator: (v) => ["start", "middle", "end"].includes(v),
+		default: "middle",
+	},
+});
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <rect
+      :width="width"
+      :height="height"
+      :stroke="color"
+      :fill="fillColor"
+      style="stroke-width: 2"
+    />
+    <text
+      :x="width / 2"
+      :y="height / 2"
+      :dy="fontSize / 2.4"
+      :fill="tColor"
+      :text-anchor="textAnchor"
+      :font-size="fontSize"
+      >{{ text }}</text
+    >
+    <text
+      v-if="unit"
+      :x="width + 10"
+      :y="height / 2"
+      :dy="fontSize / 2.4"
+      :fill="color"
+      text-anchor="start"
+      :font-size="fontSize"
+      >{{ unit }}</text
+    >
+  </g>
+</template>
+
+<style scoped lang="less"></style>
diff --git a/src/components/svg/svgLine.vue b/src/components/svg/svgLine.vue
new file mode 100644
index 0000000..a89f8a9
--- /dev/null
+++ b/src/components/svg/svgLine.vue
@@ -0,0 +1,117 @@
+<script setup>
+import { ref, computed } from "vue";
+
+const props = defineProps({
+	offset: {
+		type: Array,
+		default: () => [0, 0],
+	},
+	points: {
+		type: Array,
+		default: () => [
+			[0, 0],
+			[10, 10],
+		],
+	},
+	color: {
+		type: String,
+		default: "#804000",
+	},
+  roundCornerFlag: {
+    type: Boolean,
+    default: false
+  },
+	lineWidth: {
+		type: Number,
+		default: 2,
+	},
+	currColor: {
+		type: String,
+		default: "#0f0",
+	},
+  r: {
+    type: [Number, String],
+    default: 6
+  },
+});
+
+function calcVectorProperties(A, B) {
+  let [x0, y0] = A;
+  let [x1, y1] = B;
+  // 璁$畻鍚戦噺鍒嗛噺
+  const dx = x1 - x0;
+  const dy = y1 - y0;
+  
+  // 璁$畻鍚戦噺鐨勬ā闀匡紙娆у嚑閲屽緱璺濈锛�
+  const magnitude = Math.sqrt(dx * dx + dy * dy);
+  if (magnitude < props.r) {
+    throw new Error('涓ょ偣涔嬮棿璺濈杩囪繎');
+  }
+  
+  // 澶勭悊闆跺悜閲忔儏鍐碉紙閬垮厤闄や互闆讹級
+  const normalizedVector = magnitude === 0 
+      ? [0, 0]
+      : [ dx / magnitude, dy / magnitude ];
+  
+  // 璁$畻鍚戦噺涓巟杞存鏂瑰悜鐨勫す瑙掞紙寮у害锛�
+  // Math.atan2 杩斿洖鍊艰寖鍥达細[-蟺, 蟺]
+  const angleRadians = Math.atan2(normalizedVector[1], normalizedVector[0]);
+
+  // AB涓婄B鐐圭殑璺濈r璺濈鐨勭偣鐨勫潗鏍囷紙x2, y2锛�
+  const x2 = x1 - props.r * normalizedVector[0];
+  const y2 = y1 - props.r * normalizedVector[1];
+  
+  return {
+    normalizedVector,
+    angleRadians,
+    x2,
+    y2
+  };
+}
+
+
+const path = computed(() => {
+	let points = props.points;
+	let path = `M ${points[0].join(",")} `;
+	for (let i = 1, len = points.length; i < len; i++) {
+    let str = '';
+    let p0 = points[i - 1],
+    p1 = points[i],
+    p2 = points[i + 1];
+
+    if (props.roundCornerFlag) {
+      if (i == len - 1) {
+        str = ` L ${p1.join(",")} `;
+      } else {
+        let { x2, y2 } = calcVectorProperties(p0, p1);
+        let { x2: x3, y2: y3 } = calcVectorProperties(p2, p1);
+        str = ` L ${x2},${y2} Q ${p1.join(",")} ${x3},${y3} `;
+      }
+    } else {
+      str = ` L ${points[i].join(",")} `
+    }
+		path += str;
+	}
+	return path;
+});
+const pointsStr = computed(() => {
+	return props.points.join(" ");
+});
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <!-- <defs>
+        <circle id="ball" :r="ballR" />
+      </defs> -->
+    <!-- <polyline
+      :points="pointsStr"
+      :stroke="color"
+      :stroke-width="lineWidth"
+      style="fill: none"
+    /> -->
+    <path :d="path" :stroke="color" :stroke-width="lineWidth" style="fill: none;"></path>
+  </g>
+</template>
+
+<style scoped lang="less"></style>
diff --git a/src/components/svg/svgLock.vue b/src/components/svg/svgLock.vue
new file mode 100644
index 0000000..a107e18
--- /dev/null
+++ b/src/components/svg/svgLock.vue
@@ -0,0 +1,39 @@
+<script setup>
+import { ref } from "vue";
+const props = defineProps({
+  offset: {
+      type: Array,
+      default() {
+        return [0, 0];
+      },
+    },
+    stateColor: {
+      type: String,
+      default: "#804000",
+    },
+    lockFillColor: {
+      type: String,
+      default: "#804000",
+    },
+});
+
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <rect
+      :width="8"
+      :height="20"
+      stroke="transparent"
+      fill="transparent"
+      style="stroke-width: 2"
+      ref="lock0"
+    />
+    <rect x="1" y="0" width="6" height="6" rx="2" ry="2" :fill="stateColor" stroke="none" />
+    <path d="M809.6 416.64h-53.76V308.48c0-135.68-108.096-243.84-243.2-243.84-135.04 0-243.2 108.16-243.2 243.84v108.16h-53.76c-29.44 0-53.76 24.32-53.76 53.76v433.28c0 29.44 24.32 53.76 53.76 53.76h593.92c30.08 0 54.4-24.32 54.4-53.76V470.4c0-29.44-24.32-53.76-54.4-53.76z m-135.04 0H350.72V308.48c0-89.6 72.96-162.56 161.92-162.56a162.56 162.56 0 0 1 161.92 162.56v108.16z" :fill="lockFillColor" transform="translate(-2, 8) scale(0.012)"></path>
+  </g>
+</template>
+
+<style scoped lang="less">
+
+</style>
\ No newline at end of file
diff --git a/src/components/svg/svgModule.vue b/src/components/svg/svgModule.vue
new file mode 100644
index 0000000..cc6c4a1
--- /dev/null
+++ b/src/components/svg/svgModule.vue
@@ -0,0 +1,107 @@
+<script setup>
+import { ref, computed } from "vue";
+const props = defineProps({
+  offset: {
+    type: Array,
+    default() {
+      return [0, 0];
+    },
+  },
+  fillColor: {
+    type: String,
+    default: "#073451",
+  },
+  lineWidth: {
+    type: [Number, String],
+    default: 2,
+  },
+  color: {
+		type: String,
+		default: "#4ec5f7",
+	},
+	text: {
+		type: [String, Array],
+		default: '',
+	},
+	fontSize: {
+		type: [Number, String],
+		default: 14,
+	},
+  width: {
+    type: [Number, String],
+    default: 130,
+  },
+  height: {
+    type: [Number, String],
+    default: 130,
+  },
+  img: {
+    type: String,
+    default: '',
+  },
+  imgWidth: {
+    type: [Number, String],
+    default: 120,
+  },
+  imgHeight: {
+    type: [Number, String],
+    default: 90,
+  },
+  rx: {
+    type: [Number, String],
+    default: 8,
+  },
+  ry: {
+    type: [Number, String],
+    default: 8,
+  }
+});
+
+const imgX = computed(() => {
+  return (props.width - props.imgWidth) / 2;
+});
+
+const imgY = computed(() => {
+  return props.text ? (props.height - props.imgHeight + props.fontSize * 1.2) / 2 : (props.height - props.imgHeight) / 2;
+});
+
+const textX = computed(() => {
+  return props.width / 2;
+});
+
+const textY = computed(() => {
+  return props.fontSize * 1.2;
+});
+
+</script>
+
+<template>
+  <g :transform="'translate(' + offset.join(',') + ')'">
+    <rect
+      :width="width"
+      :height="height"
+      :stroke="color"
+      :fill="fillColor"
+      :stroke-width='lineWidth'
+      :rx="rx"
+      :ry="ry"
+    />
+    <image
+      :x="imgX"
+      :y="imgY"
+      :width="imgWidth"
+      :height="imgHeight"
+      :xlink:href="img"
+    />
+    <text
+      v-if="text"
+      :x="textX"
+      :y="textY"
+      :dy="fontSize/2"
+      :fill="color"
+      text-anchor="middle"
+      :font-size="fontSize"
+      >{{ text }}</text
+    >
+  </g>
+</template>
\ No newline at end of file
diff --git a/src/components/svg/svgPv.vue b/src/components/svg/svgPv.vue
new file mode 100644
index 0000000..2b88e7b
--- /dev/null
+++ b/src/components/svg/svgPv.vue
@@ -0,0 +1,35 @@
+<script setup>
+import { ref } from "vue";
+const props = defineProps({
+	offset: {
+		type: Array,
+		default() {
+			return [0, 0];
+		},
+	},
+  r: {
+    type: [Number, String],
+    default: 18,
+  },
+	color: {
+		type: String,
+		default: "#804000",
+	},
+});
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <circle  :r="r" :stroke="color" stroke-width="2" fill="none" />
+      <!-- :x="r * -1" -->
+    <text
+      :dy="r * 0.8"
+      :fill="color"
+      text-anchor="middle"
+      :font-size="r * 1.6"
+      >V</text
+    >
+  </g>
+</template>
+
+<style scoped lang="less"></style>
\ No newline at end of file
diff --git a/src/components/svg/svgResistor.vue b/src/components/svg/svgResistor.vue
new file mode 100644
index 0000000..dbaa6c5
--- /dev/null
+++ b/src/components/svg/svgResistor.vue
@@ -0,0 +1,38 @@
+<script setup>
+import { ref } from "vue";
+const props = defineProps({
+	offset: {
+		type: Array,
+		default() {
+			return [0, 0];
+		},
+	},
+	width: {
+		type: [Number, String],
+		default: 14,
+	},
+	height: {
+		type: [Number, String],
+		default: 34,
+	},
+	color: {
+		type: String,
+		default: "#804000",
+	},
+});
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <rect
+      :x="width / -2"
+      :width="width"
+      :height="height"
+      :stroke="color"
+      fill="none"
+      style="stroke-width: 2"
+    />
+  </g>
+</template>
+
+<style scoped lang="less"></style>
diff --git a/src/components/svg/svgStation.vue b/src/components/svg/svgStation.vue
new file mode 100644
index 0000000..0139abd
--- /dev/null
+++ b/src/components/svg/svgStation.vue
@@ -0,0 +1,221 @@
+<script setup>
+import { ref, computed, onMounted, onBeforeUnmount, } from "vue";
+import svgLine from '@/components/svg/svgLine.vue';
+import switchBox from '@/components/svg/switchBox.vue';
+import svgText from '@/components/svg/svgText.vue';
+
+
+import svgFloor from '@/components/svg/svgFloor.vue';
+import svgCabinet from '@/components/svg/svgCabinet.vue';
+
+const currentCabinetIdx = ref(-1);
+const currentLockIdx = ref(-1);
+const infoVisisble = ref(false);
+const left = ref(0);
+const top = ref(0);
+const settingVisible = ref(false);
+const viewInfo = ref({});
+
+const props = defineProps({
+  rtData: {
+    type: Array,
+    default: () => [],
+  },
+  locationInfo: {
+    type: Object,
+    default: () => ({
+      control: [],
+      all: []
+    }),
+  },
+});
+
+const cabinetsList = computed(() => {
+  return props.locationInfo.control.map(v => v.screenFlag).filter(v => v);
+});
+
+const lockStatus = computed(() => {
+  // props.rtData;
+  let arr = [];
+  for(let i = 0; i < 20; i++) {
+    let lock1 = getInfo(i + 1, 1);
+    let lock2 = getInfo(i + 1, 2);
+    arr.push([lock1, lock2]);
+  }
+  // console.log('arr', arr, '=============');
+  
+  return arr;
+});
+
+function getInfo(idx, doorIdx) {
+  let lock = props.locationInfo.control.filter(v=>v.screenFlag == idx && v.addressFlag == doorIdx);
+  let lockId;
+  if (lock.length) {
+    lockId = lock[0].lockId;
+  }
+  let info = props.rtData.filter(v => v.lockId == lockId);
+  // console.log('idx', idx, doorIdx, info.length ? info[0] : {}, '=============');
+  
+  return info.length ? info[0] : {};
+}
+
+
+function showInfo(data, idx) {
+  // console.log(data, idx);
+  currentCabinetIdx.value = idx;
+  currentLockIdx.value = data.idx;
+  left.value = data.event.clientX + 'px';
+  top.value = data.event.clientY + 'px';
+  // console.log('top, left', top.value, left.value, '=============');
+
+  viewInfo.value = getInfo(idx + 1, data.idx + 1);
+  // console.log('viewInfo', viewInfo.value, '=============');
+  
+  if (!viewInfo.value.lockId) return;
+  infoVisisble.value = true;
+}
+
+function hideInfo() {
+  infoVisisble.value = false;
+}
+
+onMounted(() => {
+  window.addEventListener('resize', hideInfo);
+});
+
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', hideInfo);
+});
+
+defineExpose({
+  hideInfo
+});
+
+</script>
+
+<template>
+  <svg
+    v-bind="$attrs"
+    width="100%"
+    height="100%"
+    viewBox="0 0 1566 712"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    style="position: relative"
+  >
+    
+    <svg-text
+      :offset="[783, 44]"
+      textAnchor="middle"
+      fontSize="28"
+      text="鏈烘埧鏈烘煖"
+    ></svg-text>
+
+    <svg-floor
+      :offset="[0, 0]"
+      :points="[
+        [320, 210],
+        [1410, 210],
+        [ 1210, 570],
+        [10, 570],
+      ]"
+    ></svg-floor>
+
+    <svg-cabinet
+      v-for="(i, idx) in 10"
+      :key="'list0_' + idx"
+      :offset="[300 + idx * 110, 100]"
+      :disabled="cabinetsList.every(v => v !== (idx + 1))"
+      :onLine0="lockStatus[idx][0]['lockOnline']"
+      :onLine1="lockStatus[idx][1]['lockOnline']"
+      :lock0="lockStatus[idx][0]['lockState'] == 0"
+      :lock1="lockStatus[idx][1]['lockState'] == 0"
+      @showInfo="(eData) => showInfo(eData, idx)"
+    ></svg-cabinet>
+    
+    <svg-cabinet
+      v-for="(i, idx) in 10"
+      :key="'list1_' + idx"
+      :offset="[40 + idx * 120, 400]"
+      :disabled="cabinetsList.every(v => v !== (idx + 11))"
+      :onLine0="lockStatus[idx + 10][0]['lockOnline']"
+      :onLine1="lockStatus[idx + 10][1]['lockOnline']"
+      :lock0="lockStatus[idx + 10][0]['lockState'] == 0"
+      :lock1="lockStatus[idx + 10][1]['lockState'] == 0"
+      @showInfo="(eData) => showInfo(eData, idx + 10)"
+    ></svg-cabinet>
+    <div class="setting"></div>
+  </svg>
+
+  <teleport to='body' v-if="infoVisisble">
+    <div class="info" v-if="infoVisisble" :style="{ left, top }">
+      <!-- 绗瑊{ currentCabinetIdx + 1 }}涓煖
+      {{ currentLockIdx == 0 ? "鍓嶉棬" : "鍚庨棬" }}閿� -->
+      <div class="item lock-id">
+        <div class="label">閿佸叿ID:</div>
+        <div class="value">{{ viewInfo.lockId }}</div>
+        </div>
+      <div class="item lock-name">
+        <div class="label">閿佸叿鍚嶇О:</div>
+        <div class="value">{{ viewInfo.lockName }}</div>
+      </div>
+      <div class="item online">
+        <div class="label">鍦ㄧ嚎鐘舵��:</div>
+        <div class="value">{{ viewInfo.lockOnline ? "鍦ㄧ嚎" : "绂荤嚎" }}</div>
+      </div>
+      <div class="item is-lock" v-if="viewInfo.lockOnline">
+        <div class="label">閿佸叿鐘舵��:</div>
+        <div class="value">{{ viewInfo.lockState == 0 ? "宸查棴閿�" : "宸插紑閿�" }}</div>
+      </div>
+      <div class="item" v-if="viewInfo.lockOnline">
+        <div class="label">钃濈墮鐘舵��:</div>
+        <div class="value">{{ viewInfo.blState == 0 ? '钃濈墮鍏抽棴' : '钃濈墮寮�鍚�' }}</div>
+      </div>
+    </div>
+    <div class="mask" @mouseenter="hideInfo"></div>
+  </teleport>
+</template>
+
+<style scoped lang="less">
+.info {
+  position: absolute;
+  background: #0ff;
+  color: #000;
+  z-index: 999999;
+  cursor: pointer;
+  padding: 8px;
+  transform: translate(-100%, -100%);
+  border-radius: 4px;
+  .item {
+    display: flex;
+    align-items: center;
+    .label {
+      width: 5em;
+      margin-right: 0.4em;
+      text-align: right;
+    }
+    .value {
+      flex: 1;
+    }
+  }
+  &::after {
+    content: '';
+    position: absolute;
+    left: 80%;
+    top: 80%;
+    width: 22%;
+    height: 22%;
+    z-index: -1;
+  }
+}
+.mask {
+  position: fixed;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  // background: rgba(0, 0, 0, 0.4);
+  z-index: 999998;
+}
+
+</style>
diff --git a/src/components/svg/svgStatus.vue b/src/components/svg/svgStatus.vue
new file mode 100644
index 0000000..3844ff4
--- /dev/null
+++ b/src/components/svg/svgStatus.vue
@@ -0,0 +1,57 @@
+<script setup>
+import { ref } from "vue";
+const colors = ['#0f0', '#f00'];
+
+const props = defineProps({
+  r: {
+    type: [Number, String],
+    default: 6,
+  },
+	offset: {
+		type: Array,
+		default: () => [0, 0],
+	},
+  text: {
+    type: String,
+    default: 'Text',
+  },
+  fontSize: {
+    type: [Number, String],
+    default: 16,
+  },
+	color: {
+		type: String,
+		default: "#fff",
+	},
+	// 鐘舵�� 0姝e父 1鏁呴殰
+	status: {
+		type: Number,
+		default: 0,
+	},
+
+});
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <circle
+      :cx="r"
+      :cy="r"
+      :r="r"
+      :stroke="status ? colors[1] : colors[0]"
+      stroke-width="2"
+      :fill="status ? colors[1] : 'none'"
+    />
+    <text
+      :x="r * 2 + fontSize"
+      :y="r"
+      :dy="fontSize/2.6"
+      :fill="color"
+      text-anchor="start"
+      :font-size="fontSize"
+      >{{ text }}</text
+    >
+  </g>
+</template>
+
+<style scoped lang="less"></style>
diff --git a/src/components/svg/svgSwitch.vue b/src/components/svg/svgSwitch.vue
new file mode 100644
index 0000000..77f0029
--- /dev/null
+++ b/src/components/svg/svgSwitch.vue
@@ -0,0 +1,117 @@
+<script setup name="SvgSwitch">
+import { ref, computed, onMounted, watch, nextTick, } from "vue";
+const props = defineProps({
+  r: {
+    type: Number,
+    default: 3,
+  },
+  len: {
+    type: Number,
+    default: 60,
+  },
+  offset: {
+    type: Array,
+    default() {
+      return [0, 0];
+    },
+  },
+  isVertical: {
+    type: Boolean,
+    default: false,
+  },
+  state: {
+    type: Boolean,
+    defalut: false,
+  },
+  points: {
+    type: Array,
+    default() {
+      return [
+        [0, 0],
+        [10, 10],
+      ];
+    },
+  },
+  color: {
+    type: String,
+    default: "#f59a23",
+  },
+  currColor: {
+    type: String,
+    default: "#0f0",
+  },
+  lineWidth: {
+    type: [Number, String],
+    default: 2,
+  },
+  turnStart: {
+    type: Boolean,
+    default: true
+  }
+});
+
+const cx = computed(() => {
+  let { isVertical, len } = props;
+  return isVertical ? 0 : len;
+});
+const cy = computed(() => {
+  let { isVertical, len } = props;
+  return isVertical ? len : 0;
+});
+
+const pointsStr = computed(() => {
+  return props.points.join(" ");
+});
+
+const fromDeg = ref(-2);
+const toDeg = ref(-38);
+
+watch(
+  () => props.state,
+  (n) => {
+    nextTick(() => {
+      changeSwitch();
+    });
+  },
+  { immediate: true }
+);
+
+const anim = ref(null);
+
+function changeSwitch() {
+  if (props.state) {
+    fromDeg.value = -38;
+    toDeg.value = -2;
+  } else {
+    fromDeg.value = -2;
+    toDeg.value = -38;
+  }
+  // 閲嶆柊寮�鍚姩鐢�
+  anim.value?.beginElement();
+}
+
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <circle :r="r" style="fill: #fff; stroke: none"></circle>
+    <circle :r="r" :cx="cx" :cy="cy" style="fill: #fff; stroke: none"></circle>
+    <line :x1="turnStart ? 0 : cx" :y1="turnStart ? 0 : cy" :x2="turnStart ? cx : 0" :y2="turnStart ? cy : 0" :stroke-width="lineWidth" :stroke="color">
+      <animateTransform
+        ref="anim"
+        attributeName="transform"
+        attributeType="XML"
+        type="rotate"
+        :from="[fromDeg, turnStart ? 0 : cx, turnStart ? 0 : cy].join(',')"
+        :to="[toDeg, turnStart ? 0 : cx, turnStart ? 0 : cy].join(',')"
+        dur="0.3s"
+        repeatCount="1"
+        fill="freeze"
+      />
+    </line>
+  </g>
+</template>
+
+
+<style scoped lang="less">
+</style>
diff --git a/src/components/svg/svgText.vue b/src/components/svg/svgText.vue
new file mode 100644
index 0000000..8d0217a
--- /dev/null
+++ b/src/components/svg/svgText.vue
@@ -0,0 +1,63 @@
+<script setup>
+import { ref } from "vue";
+
+const props = defineProps({
+	offset: {
+		type: Array,
+		default() {
+			return [0, 0];
+		},
+	},
+	color: {
+		type: String,
+		default: "#fff",
+	},
+	text: {
+		type: [String, Array],
+		default: 'Text',
+	},
+	fontSize: {
+		type: [Number, String],
+		default: 16,
+	},
+	textAnchor: {
+		type: String,
+		validator: (v) => ["start", "middle", "end"].includes(v),
+		default: 'start',
+	},
+  isVertical: {
+    type: Boolean,
+    default: false
+  }
+});
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <text
+      v-if="typeof props.text === 'string'"
+      :dy="fontSize/2"
+      :fill="color"
+      :text-anchor="textAnchor"
+      :font-size="fontSize"
+      :writing-mode="isVertical ? 'vertical-rl' : 'horizontal-tb'"
+      style="text-orientation: upright;"
+      >{{ text }}</text
+    >
+    <text
+      v-else
+      :fill="color"
+      :text-anchor="textAnchor"
+      :font-size="fontSize"
+      :writing-mode="isVertical ? 'vertical-rl' : 'horizontal-tb'"
+      style="text-orientation: upright;"
+      >
+      <tspan :dy="fontSize / 2" :x="isVertical ? (idx - (props.text.length - 1) / 2) * fontSize * 1.2 : 0" :y="isVertical ? 0 : (idx - (props.text.length - 1) / 2) * fontSize * 1.2" v-for="(item, idx) in props.text" :key="'t_' + idx">
+        {{ item }}
+      </tspan>
+      </text
+    >
+  </g>
+</template>
+
+<style scoped lang="less"></style>
diff --git a/src/components/svg/svgTextBorderBox.vue b/src/components/svg/svgTextBorderBox.vue
new file mode 100644
index 0000000..cafd46b
--- /dev/null
+++ b/src/components/svg/svgTextBorderBox.vue
@@ -0,0 +1,89 @@
+<script setup>
+import { ref, computed } from "vue";
+
+const props = defineProps({
+	offset: {
+		type: Array,
+		default() {
+			return [0, 0];
+		},
+	},
+  borderWidth: {
+    type: [Number, String],
+    default: 6,
+  },
+	width: {
+		type: [Number, String],
+		default: 142,
+	},
+	height: {
+		type: [Number, String],
+		default: 42,
+	},
+	color: {
+		type: String,
+		default: "url(#colorBorder0)",
+	},
+	fillColor: {
+		type: String,
+		default: '#989898',
+	},
+	tColor: {
+		type: String,
+		default: "#fff",
+	},
+	text: {
+		type: [Number, String],
+		default: "Text",
+	},
+	fontSize: {
+		type: [Number, String],
+		default: 16,
+	},
+	textAnchor: {
+		type: String,
+		validator: (v) => ["start", "middle", "end"].includes(v),
+		default: "middle",
+	},
+});
+
+const _width = computed(() => {
+  return props.width - 2 * props.borderWidth;
+});
+
+const _height = computed(() => {
+  return props.height - 2 * props.borderWidth;
+});
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <rect
+      :width="width"
+      :height="height"
+      :stroke="color"
+      :fill="fillColor"
+      style="stroke-width: 1"
+    />
+    <rect
+      :x="borderWidth"
+      :y="borderWidth"
+      :width="_width"
+      :height="_height"
+      stroke="url(#colorBorder0_reverse)"
+      :fill="fillColor"
+      style="stroke-width: 1"
+    />
+    <text
+      :x="width / 2"
+      :y="height / 2"
+      :dy="fontSize / 2.6"
+      :fill="tColor"
+      :text-anchor="textAnchor"
+      :font-size="fontSize"
+      >{{ text }}</text
+    >
+  </g>
+</template>
+
+<style scoped lang="less"></style>
diff --git a/src/components/svg/svgTextBox.vue b/src/components/svg/svgTextBox.vue
new file mode 100644
index 0000000..1df0720
--- /dev/null
+++ b/src/components/svg/svgTextBox.vue
@@ -0,0 +1,68 @@
+<script setup>
+import { ref } from "vue";
+
+const props = defineProps({
+	offset: {
+		type: Array,
+		default() {
+			return [0, 0];
+		},
+	},
+	width: {
+		type: [Number, String],
+		default: 142,
+	},
+	height: {
+		type: [Number, String],
+		default: 38,
+	},
+	color: {
+		type: String,
+		default: "#0ff",
+	},
+	fillColor: {
+		type: String,
+		default: '#989898',
+	},
+	tColor: {
+		type: String,
+		default: "#fff",
+	},
+	text: {
+		type: [Number, String],
+		default: "Text",
+	},
+	fontSize: {
+		type: [Number, String],
+		default: 16,
+	},
+	textAnchor: {
+		type: String,
+		validator: (v) => ["start", "middle", "end"].includes(v),
+		default: "middle",
+	},
+});
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <rect
+      :width="width"
+      :height="height"
+      :stroke="color"
+      :fill="fillColor"
+      style="stroke-width: 2"
+    />
+    <text
+      :x="width / 2"
+      :y="height / 2"
+      :dy="fontSize / 2"
+      :fill="tColor"
+      :text-anchor="textAnchor"
+      :font-size="fontSize"
+      >{{ text }}</text
+    >
+  </g>
+</template>
+
+<style scoped lang="less"></style>
diff --git a/src/components/svg/svgTriangle.vue b/src/components/svg/svgTriangle.vue
new file mode 100644
index 0000000..64bcdcb
--- /dev/null
+++ b/src/components/svg/svgTriangle.vue
@@ -0,0 +1,39 @@
+<script setup>
+import { ref, computed } from "vue";
+const props = defineProps({
+	offset: {
+		type: Array,
+		default() {
+			return [0, 0];
+		},
+	},
+	width: {
+		type: [Number, String],
+		default: 14,
+	},
+	color: {
+		type: String,
+		default: "#804000",
+	},
+});
+
+const points = computed(() => {
+	return [
+		[props.width / -2, 0],
+		[props.width / 2, 0],
+		[0, props.width],
+	];
+});
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <polygon
+      :points="points.join(' ')"
+      :stroke="color"
+      style="fill: none; stroke-width: 2"
+    />
+  </g>
+</template>
+
+<style scoped lang="less"></style>
diff --git a/src/components/svg/switchBox.vue b/src/components/svg/switchBox.vue
new file mode 100644
index 0000000..167b5d0
--- /dev/null
+++ b/src/components/svg/switchBox.vue
@@ -0,0 +1,62 @@
+<script setup>
+import { ref, computed, } from "vue";
+const width = 18;
+const borderWidth = 2;
+const colors = ["#0d0", "#FF0000", "#804000"];
+
+const props = defineProps({
+	offset: {
+		type: Array,
+		default: () => [0, 0],
+	},
+	points: {
+		type: Array,
+		default: () => [
+			[0, 0],
+			[10, 10],
+		],
+	},
+	color: {
+		type: String,
+		default: "#804000",
+	},
+  // 寮�鍏崇姸鎬� 0 鏂紑   1 闂悎   2 鏈煡
+	status: {
+		type: Number,
+		default: 0,
+	},
+	currColor: {
+		type: String,
+		default: "#0f0",
+	},
+});
+
+const fillColor = computed(() => {
+  return props.status ? colors[props.status] : 'transparent';
+});
+
+const strokeColor = computed(() => {
+  return colors[props.status];
+});
+</script>
+
+<template>
+  <g ref="g" :transform="'translate(' + offset.join(',') + ')'">
+    <rect
+      :width="width"
+      :height="width"
+      :stroke="`url(${props.status == 2 ? '#colorBorder1' : '#colorBorder0'})`"
+      stroke-width="2"
+    />
+    <rect
+      x="2"
+      y="2"
+      :width="width - 4"
+      :height="width - 4"
+      :fill="fillColor"
+      :stroke="strokeColor"
+    />
+  </g>
+</template>
+
+<style scoped lang="less"></style>
diff --git a/src/components/svgDiagram.vue b/src/components/svgDiagram.vue
new file mode 100644
index 0000000..5a53bda
--- /dev/null
+++ b/src/components/svgDiagram.vue
@@ -0,0 +1,588 @@
+<script setup>
+import { ref, computed, onMounted, onBeforeUnmount } from "vue";
+import svgLine from '@/components/svg/svgLine.vue';
+import svgDotRect from '@/components/svg/svgDotRect.vue';
+import svgText from '@/components/svg/svgText.vue';
+import svgSwitch from '@/components/svg/svgSwitch.vue';
+import svgImg from '@/components/svg/svgImg.vue';
+import svgArc from '@/components/svg/svgArc.vue';
+import svgDiode from '@/components/svg/svgDiode.vue';
+import svgModule from '@/components/svg/svgModule.vue';
+import svgFlow from '@/components/svg/svgFlow.vue';
+
+import imgAcdc from '@/assets/images/img-acdc.png';
+import imgBatt from '@/assets/images/img-batt.png';
+import imgCharge from '@/assets/images/img-charge.png';
+import imgHr from '@/assets/images/img-hr.png';
+
+const lineColor = "#4cb5e2";
+const lineColor1 = "#ed973e";
+const switchColor = "#f00";
+const switchWidth = 40;
+const diodeWidth = 20;
+const disColor = "#f00";
+const charColor = "#0f0";
+
+// 鏍稿鏀剧數
+const line0 = [
+  {
+    points: [
+      [932, 260],
+      [932, 230],
+      [1262, 230],
+      [1262, 260],
+    ],
+    roundCornerFlags: [false, false],
+    lineColor: disColor
+  },
+  {
+    points: [
+      [1262, 390],
+      [1262, 420],
+      [932, 420],
+      [932, 390],
+    ],
+    roundCornerFlags: [true, false],
+    lineColor: disColor
+  }
+];
+
+// 鏍稿鍏呯數
+const line1 = [
+  {
+    points: [
+      [682, 79],
+      [682, 50],
+      [1262, 50],
+      [1262, 70],
+    ],
+    roundCornerFlags: [true, true],
+    lineColor: charColor
+  },
+  {
+    points: [
+      [1262, 200],
+      [1262, 230],
+      [932, 230],
+      [932, 260],
+    ],
+    roundCornerFlags: [false, false],
+    lineColor: charColor
+  },
+  {
+    points: [
+      [932, 390],
+      [932, 420],
+      [770, 420],
+      [770, 359],
+    ],
+    roundCornerFlags: [false, true],
+    lineColor: charColor
+  }
+];
+
+// 鐩磋繛鍏呯數
+const line2 = [
+  {
+    points: [
+      [682, 79],
+      [682, 50],
+      [932, 50],
+      [932, 260],
+    ],
+    roundCornerFlags: [true, false],
+    lineColor: charColor
+  },
+  {
+    points: [
+      [932, 390],
+      [932, 420],
+      [770, 420],
+      [770, 359],
+    ],
+    roundCornerFlags: [false, true],
+    lineColor: charColor
+  }
+];
+// 鍋滅數鏀剧數
+const line3 = [
+  {
+    points: [
+      [932, 260],
+      [932, 174],
+      [1006, 174],
+      [1006, 80],
+      [932, 80],
+      [932, 50],
+      [682, 50],
+      [682, 79],
+    ],
+    roundCornerFlags: [false, true, true, false, false, true],
+    lineColor: disColor
+  },
+  {
+    points: [
+      [770, 359],
+      [770, 420],
+      [932, 420],
+      [932, 390],
+    ],
+    roundCornerFlags: [true, false],
+    lineColor: disColor
+  }
+];
+
+const state_k1 = ref(true);
+const state_k2 = ref(true);
+const state_k3 = ref(true);
+const currentCabinetIdx = ref(-1);
+const currentLockIdx = ref(-1);
+const infoVisisble = ref(false);
+const left = ref(0);
+const top = ref(0);
+const settingVisible = ref(false);
+const viewInfo = ref({});
+
+const props = defineProps({
+  state: {
+    type: [Number, String],
+    default: 3,
+  },
+});
+
+const flowList = computed(() => {
+  let res = [];
+
+  switch(props.state) {
+    // 鏍稿鏀剧數
+    case 0:
+      res = line0;
+      break;
+    // 鏍稿鍏呯數
+    case 1:
+      res = line1;
+      break;
+    // 鐩磋繛鍏呯數
+    case 2:
+      res = line2;
+      break;
+    // 鍋滅數鏀剧數
+    case 3:
+      res = line3;
+      break;
+  }
+
+  return res;
+});
+
+</script>
+
+<template>
+  <svg
+    v-bind="$attrs"
+    width="100%"
+    height="100%"
+    viewBox="0 0 1350 440"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    style="position: relative"
+  >
+    <svg-dot-rect
+      :offset="[18, 36]"
+      width="720"
+      height="384"
+      color="#fff"
+      fillColor="transparent"
+      :rx="8"
+      :ry="8"
+      :strokeWidth="2"
+    ></svg-dot-rect>
+    <svg-line
+      :points="[
+        [58, 130],
+        [134, 130],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-switch
+      :offset="[134, 130]"
+      :len="switchWidth"
+      :color='switchColor'
+    ></svg-switch>
+    <svg-line
+      :points="[
+        [174, 130],
+        [236, 130],
+        [236, 218],
+      ]"
+      roundCornerFlag
+      :color='lineColor'
+    ></svg-line>
+
+    <svg-line
+      :points="[
+        [58, 218],
+        [134, 218],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-switch
+      :offset="[134, 218]"
+      :len="switchWidth"
+      :color='switchColor'
+    ></svg-switch>
+    <svg-line
+      :points="[
+        [174, 218],
+        [272, 218],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <!-- 124 194 -->
+    <svg-dot-rect
+      :offset="[252, 124]"
+      width="124"
+      height="194"
+      :color="lineColor"
+      fillColor="transparent"
+      strokeDasharray="none"
+      :rx="8"
+      :ry="8"
+      :strokeWidth="2"
+    ></svg-dot-rect>
+    <!-- 32 106 -->
+    <svg-line
+      :points="[
+        [304, 164],
+        [272, 164],
+        [272, 270],
+        [304, 270],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-switch
+      :offset="[304, 164]"
+      :len="switchWidth"
+      :color='switchColor'
+    ></svg-switch>
+    <svg-switch
+      :offset="[304, 270]"
+      :len="switchWidth"
+      :color='switchColor'
+    ></svg-switch>
+    <svg-line
+      :points="[
+        [344, 164],
+        [422, 164],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-line
+      :points="[
+        [344, 270],
+        [422, 270],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <!-- 336 -->
+    <svg-line
+      :points="[
+        [422, 50],
+        [422, 386],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-line
+      :points="[
+        [422, 88],
+        [472, 88],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-line
+      :points="[
+        [422, 212],
+        [472, 212],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-line
+      :points="[
+        [422, 350],
+        [472, 350],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-img
+      :img="imgAcdc"
+      :width="130"
+      :height="48"
+      :offset="[472, 64]"
+    ></svg-img>
+    <svg-img
+      :img="imgAcdc"
+      :width="130"
+      :height="48"
+      :offset="[472, 188]"
+    ></svg-img> 
+    <svg-img
+      :img="imgAcdc"
+      :width="130"
+      :height="48"
+      :offset="[472, 326]"
+    ></svg-img>
+    <!-- 18 80 -->
+    <svg-line
+      :points="[
+        [602, 79],
+        [682, 79],
+      ]"
+      :color='lineColor1'
+    ></svg-line>
+    <!-- 124 -->
+    <svg-line
+      :points="[
+        [602, 203],
+        [682, 203],
+      ]"
+      :color='lineColor1'
+    ></svg-line>
+    <!-- 262 580 20 -->
+    <svg-line
+      :points="[
+        [602, 341],
+        [682, 341],
+        [682, 50],
+        [1262, 50],
+        [1262, 70],
+      ]"
+      roundCornerFlag
+      :color='lineColor1'
+    ></svg-line>
+
+    <svg-line
+      :points="[
+        [602, 97],
+        [676, 97],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-arc
+      :offset="[676, 97]"
+      r="6"
+    ></svg-arc>
+    <svg-line
+      :points="[
+        [688, 97],
+        [770, 97],
+        [770, 420],
+        [1080, 420],
+      ]"
+      roundCornerFlag
+      :color='lineColor'
+    ></svg-line>
+    <svg-line
+      :points="[
+        [602, 221],
+        [676, 221],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-arc
+      :offset="[676, 221]"
+      r="6"
+    ></svg-arc>
+    <svg-line
+      :points="[
+        [688, 221],
+        [770, 221],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-line
+      :points="[
+        [602, 359],
+        [770, 359],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-switch
+      :offset="[1080, 420]"
+      :len="switchWidth"
+      :color='switchColor'
+      :state="state_k3"
+    ></svg-switch>
+    <svg-text
+      :offset="[1100, 382]"
+      text="K3"
+      textAnchor="middle"
+    ></svg-text>
+    <svg-line
+      :points="[
+        [1120, 420],
+        [1262, 420],
+        [1262, 390],
+      ]"
+      roundCornerFlag
+      :color='lineColor'
+    ></svg-line>
+    <!-- 250 50 -->
+    <svg-line
+      :points="[
+        [932, 50],
+        [932, 100],
+      ]"
+      :color='lineColor1'
+    ></svg-line>
+    <svg-switch
+      :offset="[932, 100]"
+      isVertical
+      :len="switchWidth"
+      :color='switchColor'
+      :state="state_k1"
+      :turnStart="false"
+    ></svg-switch>
+    <svg-text
+      :offset="[900, 120]"
+      text="K1"
+      textAnchor="end"
+    ></svg-text>
+    <svg-text
+      :offset="[1026, 120]"
+      text="D1"
+      textAnchor="start"
+    ></svg-text>
+    <svg-text
+      :offset="[1100, 150]"
+      text="娓╁害:12掳C"
+      textAnchor="middle"
+    ></svg-text>
+    <svg-text
+      :offset="[918, 240]"
+      text="0.2A"
+      textAnchor="end"
+    ></svg-text>
+    <svg-line
+      :points="[
+        [932, 140],
+        [932, 260],
+      ]"
+      :color='lineColor1'
+    ></svg-line>
+    <svg-line
+      :points="[
+        [932, 80],
+        [1006, 80],
+        [1006, 174],
+        [932, 174],
+      ]"
+      roundCornerFlag
+      :color='lineColor1'
+    ></svg-line>
+    <svg-line
+      :points="[
+        [974, 80],
+        [974, 174],
+      ]"
+      roundCornerFlag
+      :color='lineColor1'
+    ></svg-line>
+    <svg-line
+      :points="[
+        [932, 390],
+        [932, 420],
+      ]"
+      :color='lineColor'
+    ></svg-line>
+    <svg-diode
+      :offset="[1006, 114]"
+      direct="top"
+      :len="diodeWidth"
+      :color='lineColor1'
+    ></svg-diode>
+    <svg-diode
+      :offset="[974, 114]"
+      direct="top"
+      :len="diodeWidth"
+      :color='lineColor1'
+    ></svg-diode>
+    <svg-line
+      :points="[
+        [932, 230],
+        [1080, 230],
+      ]"
+      :color='lineColor1'
+    ></svg-line> 
+    <svg-line
+      :points="[
+        [1120, 230],
+        [1262, 230],
+        [1262, 200],
+      ]"
+      :color='lineColor1'
+    ></svg-line>
+    <svg-line
+      :points="[
+        [1262, 230],
+        [1262, 260],
+      ]"
+      :color='lineColor'
+    ></svg-line> 
+    <svg-switch
+      :offset="[1080, 230]"
+      :len="switchWidth"
+      :color='switchColor'
+      :state="state_k2"
+    ></svg-switch>
+    <svg-text
+      :offset="[1100, 192]"
+      text="K2"
+      textAnchor="middle"
+    ></svg-text>
+    <svg-module
+      :offset="[865, 260]"
+      :img="imgBatt"
+      text="鐢垫睜"
+    ></svg-module>
+    <svg-module
+      :offset="[1197, 70]"
+      text="鍏呯數妯″潡"
+      :img="imgCharge"
+    ></svg-module>
+    <svg-module
+      :offset="[1197, 260]"
+      text="鏍稿妯″潡"
+      :img="imgHr"
+    ></svg-module>
+    <!-- 鏂囨湰 -->
+    <svg-text
+      :offset="[58, 110]"
+      text="闃查浄"
+    ></svg-text>
+    <svg-text
+      :offset="[58, 236]"
+      text="浜ゆ祦杈撳叆"
+    ></svg-text>
+    <svg-text
+      :offset="[304, 338]"
+      text="ATS"
+    ></svg-text>
+    <svg-text
+      :offset="[422, 398]"
+      text="AC220V"
+      textAnchor="middle"
+    ></svg-text>
+    <svg-text
+      :offset="[642, 54]"
+      text="220V"
+      textAnchor="middle"
+    ></svg-text>
+    <svg-flow
+      :lineList="flowList"
+    ></svg-flow>
+  </svg>
+</template>
+
+<style scoped lang="less">
+
+</style>
\ No newline at end of file
diff --git a/src/globalComponents.js b/src/globalComponents.js
index f2ec1f2..5bb2ec4 100644
--- a/src/globalComponents.js
+++ b/src/globalComponents.js
@@ -1,7 +1,9 @@
 import card from '@/components/card.vue';
 import ycCard from '@/components/ycCard/index.vue';
+import flexLayout from '@/components/FlexLayout.vue';
 
 export function registerGlobalComponents(app) {
+  app.component('flexLayout', flexLayout);
   app.component('card', card);
   app.component('ycCard', ycCard);
 }
\ No newline at end of file
diff --git a/src/icons/svg/alarm.svg b/src/icons/svg/alarm.svg
new file mode 100644
index 0000000..52270ad
--- /dev/null
+++ b/src/icons/svg/alarm.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M891.547826 794.445913h-98.393043v-337.430261a281.822609 281.822609 0 0 0-238.992696-277.682087v-17.586087a42.162087 42.162087 0 0 0-84.368696 0v17.630609a281.822609 281.822609 0 0 0-238.992695 277.682087v337.430261h-98.393044a14.113391 14.113391 0 0 0-14.113391 14.024348v56.32c0 7.746783 6.322087 13.979826 14.113391 13.979826h298.073044c8.236522 29.651478 35.172174 51.556174 67.450434 51.556174h28.13774c32.278261 0 59.213913-21.904696 67.450434-51.556174h298.028522a13.979826 13.979826 0 0 0 14.06887-13.979826V808.514783a13.979826 13.979826 0 0 0-14.06887-14.06887z"></path></svg>
\ No newline at end of file
diff --git a/src/icons/svg/arrow.svg b/src/icons/svg/arrow.svg
new file mode 100644
index 0000000..27b0cc9
--- /dev/null
+++ b/src/icons/svg/arrow.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1035 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" ><path d="M530.850448 573.163313c32.005172-31.883636 32.111192-87.434343-0.069818-119.489939C399.437114 322.845737 268.507336 191.603071 137.436629 60.506505c-12.621576-12.625455-27.585939-20.686869-45.26804-23.540364-3.529697-0.570182-7.085253-0.999434-10.630465-1.499798-1.025293 0-2.055758 0-3.081051 0-4.035232 0.575354-8.111838 0.943838-12.091475 1.757091-33.298101 6.818909-54.838303 26.616242-63.959919 59.278222-3.201293 11.479919-2.806949 24.85398-1.135192 35.701657 1.751919 11.344162 7.11499 21.818182 14.479515 30.904889 3.444364 4.252444 7.050343 8.418263 10.918788 12.291879 111.045818 111.127273 222.142061 222.222222 333.247354 333.293899 1.444202 1.444202 3.227152 2.554828 5.187232 4.080485-2.121697 2.222545-3.475394 3.696485-4.879515 5.100606C252.653437 625.450667 145.164468 733.112889 37.416912 840.506182c-15.829333 15.773737-33.541172 31.162182-36.146424 54.273293-1.233455 10.908444-1.859232 22.111677 0 32.929616 0.919273 5.363071 3.262061 10.423596 5.494949 15.358707 23.655434 52.318384 89.514667 64.241778 130.66602 23.186101C268.668953 835.315071 399.501761 703.987071 530.850448 573.163313L530.850448 573.163313zM78.451902 35.466343l-12.091475 1.757091C70.345235 36.410182 74.421841 36.041697 78.451902 35.466343L78.451902 35.466343zM551.467498 36.441212c-38.000485 2.858667-70.434909 37.034667-71.651556 75.106263-0.79903 24.979394 8.131232 45.650747 25.691798 63.197091 111.383273 111.252687 222.661818 222.615273 334.015354 333.893818 1.534707 1.534707 3.686141 2.448808 6.595232 4.327434-3.35903 2.667313-5.116121 3.803798-6.565495 5.253172-68.050747 68.000323-136.075636 136.035556-204.100525 204.065616C591.916791 765.814949 548.391619 809.360808 504.845761 852.880808c-19.449535 19.439192-28.328081 42.706747-24.045899 70.054788 4.829091 30.919111 22.440081 52.516202 51.712 63.010909 29.450343 10.550303 56.692364 4.803232 80.49002-15.848727 1.944566-1.682101 3.798626-3.470222 5.616485-5.288081 130.121697-130.111354 259.955071-260.509737 390.546101-390.141414 33.272242-33.035636 33.454545-88.934141 1.206303-121.050505C879.016912 322.804364 748.093599 191.557818 617.028064 60.456081c-12.626747-12.626747-27.611798-20.655838-45.288727-23.505455-3.534869-0.570182-7.090424-0.994263-10.635636-1.484283-1.030465 0-2.055758 0-3.086222 0.005172C555.835013 35.799919 553.659013 36.274424 551.467498 36.441212L551.467498 36.441212zM551.467498 36.441212"></path></svg>
\ No newline at end of file
diff --git a/src/icons/svg/controls.svg b/src/icons/svg/controls.svg
new file mode 100644
index 0000000..bf0851f
--- /dev/null
+++ b/src/icons/svg/controls.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M885.7 63.6H138.3c-41.1 0-74.7 33.6-74.7 74.7v560.5c0 41.1 33.6 74.7 74.7 74.7h224.2c0 41.1-33.6 74.7-74.7 74.7-31.8 0-56.1 24.3-56.1 56.1s24.3 56.1 56.1 56.1h448.4c31.8 0 56.1-24.3 56.1-56.1s-24.3-56.1-56.1-56.1c-41.1 0-74.7-33.6-74.7-74.7h224.2c41.1 0 74.7-33.6 74.7-74.7V138.3c0-41.1-33.6-74.7-74.7-74.7zM754.9 568.1H697c-9.3 43-46.7 74.7-91.6 74.7-44.8 0-82.2-31.8-91.6-74.7H269.1c-9.3 0-18.7-9.3-18.7-18.7 0-9.3 9.3-18.7 18.7-18.7h244.8c9.3-43 46.7-74.7 91.6-74.7 44.8 0 82.2 31.8 91.6 74.7H755c9.3 0 18.7 9.3 18.7 18.7-0.1 9.3-9.5 18.7-18.8 18.7z m0-261.6H510.1c-9.3 43-46.7 74.7-91.6 74.7-44.8 0-82.2-31.8-91.6-74.7H269c-9.3 0-18.7-9.3-18.7-18.7 0-9.3 9.3-18.7 18.7-18.7h58c9.3-43 46.7-74.7 91.6-74.7 44.8 0 82.2 31.8 91.6 74.7H755c9.3 0 18.7 9.3 18.7 18.7-0.1 9.3-9.5 18.7-18.8 18.7z"></path><path d="M605.4 549.4m-56.1 0a56.1 56.1 0 1 0 112.2 0 56.1 56.1 0 1 0-112.2 0Z"></path><path d="M418.6 287.8m-56.1 0a56.1 56.1 0 1 0 112.2 0 56.1 56.1 0 1 0-112.2 0Z"></path></svg>
\ No newline at end of file
diff --git a/src/icons/svg/data.svg b/src/icons/svg/data.svg
index 05b58d4..002c363 100644
--- a/src/icons/svg/data.svg
+++ b/src/icons/svg/data.svg
@@ -1 +1 @@
-<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M896 405.333333v128c0 106.026667-171.946667 192-384 192s-384-85.973333-384-192v-128c0 106.026667 171.946667 192 384 192s384-85.973333 384-192z m-768 213.333334c0 106.026667 171.946667 192 384 192s384-85.973333 384-192v128c0 106.026667-171.946667 192-384 192s-384-85.973333-384-192v-128z m384-106.666667c-212.053333 0-384-85.973333-384-192S299.946667 128 512 128s384 85.973333 384 192-171.946667 192-384 192z" fill="#000000"></path></svg>
\ No newline at end of file
+<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M896 405.333333v128c0 106.026667-171.946667 192-384 192s-384-85.973333-384-192v-128c0 106.026667 171.946667 192 384 192s384-85.973333 384-192z m-768 213.333334c0 106.026667 171.946667 192 384 192s384-85.973333 384-192v128c0 106.026667-171.946667 192-384 192s-384-85.973333-384-192v-128z m384-106.666667c-212.053333 0-384-85.973333-384-192S299.946667 128 512 128s384 85.973333 384 192-171.946667 192-384 192z"></path></svg>
\ No newline at end of file
diff --git a/src/icons/svg/dev.svg b/src/icons/svg/dev.svg
new file mode 100644
index 0000000..c0e56a9
--- /dev/null
+++ b/src/icons/svg/dev.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M838.4 294.4v166.4H192V294.4h646.4z m44.8-76.8H147.2c-19.2 0-32 12.8-32 32v256c0 19.2 12.8 32 32 32h736c19.2 0 32-12.8 32-32v-256c0-19.2-19.2-32-32-32z m-6.4 588.8H147.2c-19.2 0-38.4-19.2-38.4-38.4s19.2-38.4 38.4-38.4h723.2c19.2 0 38.4 19.2 38.4 38.4 6.4 25.6-12.8 38.4-32 38.4z m0-128H147.2c-19.2 0-38.4-19.2-38.4-38.4s19.2-38.4 38.4-38.4h723.2c19.2 0 38.4 19.2 38.4 38.4 6.4 19.2-12.8 38.4-32 38.4z m-179.2-300.8a38.4 38.4 0 1 0 76.8 0 38.4 38.4 0 0 0-76.8 0z"></path></svg>
\ No newline at end of file
diff --git a/src/icons/svg/dev1.svg b/src/icons/svg/dev1.svg
new file mode 100644
index 0000000..b0bce23
--- /dev/null
+++ b/src/icons/svg/dev1.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M64 192l128 0 0 64 448 0 0-64 128 0 0 256 64 0 0-320-192 0 0-64-64 0 0-64-320 0 0 64-64 0 0 64-192 0 0 896 128 0 0-64-64 0 0-768zM256 128l64 0 0-64 192 0 0 64 64 0 0 64-320 0 0-64zM192 512l0 448 128 0 0 64 128 0 0-64 320 0 0 64 128 0 0-64 128 0 0-448-832 0zM960 896l-704 0 0-320 704 0 0 320zM384 704l-64 0 0-64 64 0 0 64zM512 704l-64 0 0-64 64 0 0 64zM640 704l-64 0 0-64 64 0 0 64zM384 832l-64 0 0-64 64 0 0 64zM512 832l-64 0 0-64 64 0 0 64zM640 832l-64 0 0-64 64 0 0 64zM832 832l64 0 0-192-192 0 0 192 128 0zM768 704l64 0 0 64-64 0 0-64z"></path></svg>
\ No newline at end of file
diff --git a/src/icons/svg/realtime1.svg b/src/icons/svg/realtime1.svg
new file mode 100644
index 0000000..b354973
--- /dev/null
+++ b/src/icons/svg/realtime1.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1280 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M640.00064 0c353.472 0 640 286.528 640 640 0 128.576-37.952 248.32-103.168 348.544H103.16864A636.992 636.992 0 0 1 0.00064 640C0.00064 286.528 286.52864 0 640.00064 0z m0 205.76c-119.04 0-231.04 46.4-315.136 130.56a442.816 442.816 0 0 0-130.56 315.136 45.696 45.696 0 0 0 91.392 0 352 352 0 0 1 103.808-250.496A352 352 0 0 1 640.00064 297.216a352 352 0 0 1 250.496 103.744 352 352 0 0 1 103.808 250.496 45.696 45.696 0 0 0 91.392 0c0-119.04-46.336-230.976-130.56-315.136A442.816 442.816 0 0 0 640.00064 205.76z m28.928 406.336a113.92 113.92 0 0 0-41.728 7.872L528.51264 521.28a45.696 45.696 0 1 0-64.64 64.64l98.688 98.752a114.304 114.304 0 1 0 106.368-72.576z"></path></svg>
\ No newline at end of file
diff --git a/src/icons/svg/setting.svg b/src/icons/svg/setting.svg
new file mode 100644
index 0000000..ab39b83
--- /dev/null
+++ b/src/icons/svg/setting.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M890.53893 559.569946c2.114575-16.294021 3.482653-32.587042 3.482653-49.999309 0-17.288351-1.61691-33.582371-3.482653-49.999309l107.461589-84.078346c9.701463-7.587887 12.313703-21.393584 6.094893-32.588041l-101.864361-176.241244c-6.094893-11.192458-19.90059-15.297692-31.094048-11.192458l-126.864515 50.993639c-26.492149-20.397256-55.098873-37.187942-86.068005-49.999309L638.801558 21.392585C637.185646 9.079882 626.612771 0 613.802403 0H409.948765c-12.686452 0-23.384243 9.203798-25.000154 21.392585l-19.402926 135.072984c-31.094048 12.686452-59.575856 30.099718-86.068005 49.999309l-126.864515-50.994638c-11.815039-4.601899-24.999155 0-31.094048 11.194457L19.655755 342.90594c-6.715475 11.193458-3.481654 24.999155 6.094893 32.587042l107.46159 84.078346c-2.114575 16.293021-3.482653 33.083707-3.482653 49.999309 0 16.914602 1.61691 33.581372 3.482653 49.999309L25.626732 643.648292C15.92427 651.235179 13.313029 665.040877 19.531839 676.234334l101.864361 176.241244c6.093894 11.194457 19.90059 15.298691 31.094048 11.194457l126.864515-50.995638c26.492149 20.398255 55.097874 37.18994 86.068005 50.000309l19.401926 135.072984c1.61691 12.188787 12.189786 21.392585 25.000154 21.392585h203.852639c12.686452 0 23.383244-9.203798 25.000154-21.392585l19.401926-135.072984c31.094048-12.686452 59.576855-30.099718 86.069005-49.99931l126.864515 50.994639c11.815039 4.601899 24.999155 0 31.093049-11.194457L1003.970496 676.234334c6.094893-11.193458 3.482653-24.999155-6.093893-32.586042l-107.337673-84.078346zM511.938042 688.050372c-98.381708 0-178.355819-79.974111-178.355819-178.355819s79.974111-178.355819 178.355819-178.355818S690.293861 411.312846 690.293861 509.694553s-79.974111 178.355819-178.355819 178.355819z"></path></svg>
\ No newline at end of file
diff --git a/src/icons/svg/stations.svg b/src/icons/svg/stations.svg
new file mode 100644
index 0000000..e3755e5
--- /dev/null
+++ b/src/icons/svg/stations.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" ><path d="M1024 733v131c0 53-43 96-96 96h-96c-53 0-96-43-96-96V733c0-53 43-96 96-96 8.8 0 16-7.2 16-16v-77c0-17.7-14.3-32-32-32H560c-8.8 0-16 7.2-16 16v94c0 8.3 6.7 15 15 15h1c53 0 96 43 96 96v131c0 53-43 96-96 96h-96c-53 0-96-43-96-96V733c0-53 43-96 96-96h1c8.3 0 15-6.7 15-15v-94c0-8.8-7.2-16-16-16H208c-17.7 0-32 14.3-32 32v77c0 8.8 7.2 16 16 16 53 0 96 43 96 96v131c0 53-43 96-96 96H96c-53 0-96-43-96-96V733c0-53 43-96 96-96 8.8 0 16-7.2 16-16v-77c0-53 43-96 96-96h256c8.8 0 16-7.2 16-16v-48h-96c-53 0-96-43-96-96V144c0-53 43-96 96-96h256c53 0 96 43 96 96v144c0 53-43 96-96 96h-96v48c0 8.8 7.2 16 16 16h256c53 0 96 43 96 96v77c0 8.8 7.2 16 16 16 53 0 96 43 96 96z"></path></svg>
\ No newline at end of file
diff --git a/src/icons/svg/tongji.svg b/src/icons/svg/tongji.svg
new file mode 100644
index 0000000..361b6ca
--- /dev/null
+++ b/src/icons/svg/tongji.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M912.6912 861.8496h-798.72a30.72 30.72 0 1 0 0 61.44h798.72a30.72 30.72 0 0 0 0-61.44z"></path><path d="M651.264 121.856m56.3712 0l85.248 0q56.3712 0 56.3712 56.3712l0 553.9328q0 56.3712-56.3712 56.3712l-85.248 0q-56.3712 0-56.3712-56.3712l0-553.9328q0-56.3712 56.3712-56.3712Z"></path><path d="M411.8016 288.3072m56.3712 0l85.248 0q56.3712 0 56.3712 56.3712l0 387.4816q0 56.3712-56.3712 56.3712l-85.248 0q-56.3712 0-56.3712-56.3712l0-387.4816q0-56.3712 56.3712-56.3712Z"></path><path d="M154.0608 365.7216m56.3712 0l85.248 0q56.3712 0 56.3712 56.3712l0 310.0672q0 56.3712-56.3712 56.3712l-85.248 0q-56.3712 0-56.3712-56.3712l0-310.0672q0-56.3712 56.3712-56.3712Z"></path></svg>
\ No newline at end of file
diff --git a/src/icons/svg/workstatus.svg b/src/icons/svg/workstatus.svg
new file mode 100644
index 0000000..8614329
--- /dev/null
+++ b/src/icons/svg/workstatus.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M624.1 376.9L430.7 446c1.3 7.3 2.3 14.8 2.3 22.5 0 29.1-10.3 55.5-26.9 76.7l88 95.3c18.5-12 40.5-19.1 64.2-19.1 6.9 0 13.5 0.9 20 2l76.8-215.5c-12.6-7.8-23.2-18.4-31-31z"></path><path d="M512 0C229.2 0 0 229.2 0 512s229.2 512 512 512 512-229.2 512-512S794.8 0 512 0z m193.6 422.5c-4.2 0-8.3-0.7-12.4-1.2l-76.9 215.8c35.9 20.3 60.4 58.4 60.4 102.7 0 65.4-53 118.4-118.4 118.4s-118.4-53-118.4-118.4c0-27.1 9.4-51.7 24.7-71.7L376.7 573c-19.8 13.1-43.5 20.8-69 20.8-69.2 0-125.3-56.1-125.3-125.3 0-69.2 56.1-125.3 125.3-125.3 47.2 0 87.8 26.4 109.1 64.9l193.9-69.2c-0.5-4.1-1.3-8.2-1.3-12.4 0-53.1 43.1-96.2 96.2-96.2s96.2 43.1 96.2 96.2-43 96-96.2 96z"></path></svg>
\ No newline at end of file
diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue
index 6a64c48..936c590 100644
--- a/src/layout/components/AppMain.vue
+++ b/src/layout/components/AppMain.vue
@@ -1,9 +1,9 @@
 <template>
-  <section class="app-main">
+  <section :class="['app-main', {'no-decoration': route.meta.decoration === false}]">
     <router-view v-slot="{ Component, route }">
       <transition name="fade-transform" mode="out-in">
         <keep-alive :include="cachedViews">
-          <component :is="Component" :key="route.fullPath" />
+          <component :is="Component" :key="route.name" />
         </keep-alive>
       </transition>
     </router-view>
@@ -14,6 +14,10 @@
 import { defineComponent } from 'vue';
 import { useTagsViewStore } from '@/store/tagsView';
 import { storeToRefs } from 'pinia';
+import { useRoute, useRouter } from "vue-router";
+const route = useRoute();
+const router = useRouter();
+
 const tagsViewStore = useTagsViewStore();
 const { cachedViews } = storeToRefs(tagsViewStore);
 
@@ -35,10 +39,7 @@
 
 .hasTagsView {
   .app-main {
-    /* 84 = navbar + tags-view = 50 + 34 */
-    // height: 100vh;
-
-    height: calc(100vh - 148px);
+    // height: calc(100vh - 148px);
     background: url("@/assets/images/bg-side.png") calc(100% + 8px) top e('/') auto 80% no-repeat;
     // position: relative;
     z-index: 0;
@@ -53,6 +54,12 @@
       transform: scaleX(-1);
       background: url("@/assets/images/bg-side.png") calc(100% + 8px) top e('/') auto 80% no-repeat;
     }
+    &.no-decoration {
+      background: transparent;
+      &::before {
+        background: transparent;
+      }
+    }
   }
 
   // .fixed-header+.app-main {
diff --git a/src/layout/index.vue b/src/layout/index.vue
index 9f28ae7..b2afd95 100644
--- a/src/layout/index.vue
+++ b/src/layout/index.vue
@@ -13,7 +13,7 @@
       </div>
       <tags-view class="tags-view" v-if="needTagsView" />
       <div class="main-wrap pos-r">
-        <app-main class="app-main pos-full" />
+        <app-main class="pos-full" />
       </div>
       <!-- <right-panel v-if="showSettings">
           <settings />
@@ -50,7 +50,7 @@
 const route = useRoute();
 const router = useRouter();
 
-console.log('TagsView', TagsView, '=============');
+console.log('TagsView', TagsView, route , '=============');
 
 
 const appStore = useAppStore();
diff --git a/src/router/index.js b/src/router/index.js
index e694f9b..506b8bd 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -5,6 +5,8 @@
 // import generalRouter from './modules/general';
 import systemRouter from './modules/system';
 import datasRouter from './modules/datas';
+import alarmRouter from './modules/alarm';
+import statisticsRouter from './modules/statistics';
 /* Layout */
 const Layout = () => import('@/layout/index.vue');
 
@@ -79,6 +81,8 @@
   // generalRouter,
   systemRouter,
   datasRouter,
+  alarmRouter,
+  statisticsRouter,
   // 404 page must be placed at the end !!!
   { path: '/:pathMatch(.*)*', redirect: '/404', meta: { hidden: true }}
 ];
diff --git a/src/router/modules/alarm.js b/src/router/modules/alarm.js
new file mode 100644
index 0000000..5924905
--- /dev/null
+++ b/src/router/modules/alarm.js
@@ -0,0 +1,34 @@
+const Layout = () => import('@/layout/index.vue');
+
+const systemRouter = {
+  path: '/alarm',
+  component: Layout,
+  redirect: 'noRedirect',
+  name: 'alarm',
+  meta: {
+    title: '鍛婅绠$悊',
+    icon: 'alarm'
+  },
+  children: [
+    {
+      path: 'batt',
+      component: () => import('@/views/alarm/battAlarm.vue'),
+      name: 'battAlarm',
+      meta: { title: '鐢垫睜瀹炴椂鍛婅', icon: 'alarm', noCache: false }
+    },
+    {
+      path: 'dev',
+      component: () => import('@/views/alarm/devAlarm.vue'),
+      name: 'devAlarm',
+      meta: { title: '璁惧瀹炴椂鍛婅', icon: 'alarm', noCache: false }
+    },
+    {
+      path: 'pwr',
+      component: () => import('@/views/alarm/pwrAlarm.vue'),
+      name: 'pwrAlarm',
+      meta: { title: '鐢垫簮瀹炴椂鍛婅', icon: 'alarm', noCache: false }
+    },
+  ]
+};
+
+export default systemRouter;
diff --git a/src/router/modules/datas.js b/src/router/modules/datas.js
index 741ed2e..1cc00ef 100644
--- a/src/router/modules/datas.js
+++ b/src/router/modules/datas.js
@@ -14,13 +14,13 @@
       path: 'realtime',
       component: () => import('@/views/realtime/index.vue'),
       name: 'realtime',
-      meta: { title: '瀹炴椂鐩戞祴', icon: 'component', noCache: false }
+      meta: { title: '瀹炴椂鐩戞祴', icon: 'realtime1', noCache: false, decoration: false }
     },
     {
       path: 'device',
       component: () => import('@/views/datas/device.vue'),
       name: 'device',
-      meta: { title: '璁惧绠$悊', icon: 'component', noCache: false }
+      meta: { title: '璁惧绠$悊', icon: 'dev1', noCache: false }
     },
   ]
 };
diff --git a/src/router/modules/statistics.js b/src/router/modules/statistics.js
new file mode 100644
index 0000000..4b69287
--- /dev/null
+++ b/src/router/modules/statistics.js
@@ -0,0 +1,40 @@
+const Layout = () => import('@/layout/index.vue');
+
+const systemRouter = {
+  path: '/statistics',
+  component: Layout,
+  redirect: 'noRedirect',
+  name: 'Statistics',
+  meta: {
+    title: '缁熻绠$悊',
+    icon: 'tongji'
+  },
+  children: [
+    // {
+    //   path: 'power',
+    //   component: () => import('@/views/statistics/power.vue'),
+    //   name: 'power',
+    //   meta: { title: '鐢垫簮缁熻', icon: 'component', noCache: false }
+    // },
+    {
+      path: 'dev-workstatus',
+      component: () => import('@/views/statistics/devWorkstatus.vue'),
+      name: 'devWorkstatus',
+      meta: { title: '璁惧宸ヤ綔鐘舵�佺粺璁�', icon: 'workstatus', noCache: false }
+    },
+    {
+      path: 'batt-hr',
+      component: () => import('@/views/statistics/battHr.vue'),
+      name: 'battHr',
+      meta: { title: '钃勭數姹犳牳瀹逛俊鎭粺璁�', icon: 'component', noCache: false }
+    },
+    {
+      path: 'station',
+      component: () => import('@/views/statistics/station.vue'),
+      name: 'station',
+      meta: { title: '绔欑偣缁熻', icon: 'stations', noCache: false }
+    },
+  ]
+};
+
+export default systemRouter;
diff --git a/src/styles/blue.css b/src/styles/blue.css
index 5448ec5..8e65b8a 100644
--- a/src/styles/blue.css
+++ b/src/styles/blue.css
@@ -3,7 +3,7 @@
   --light-color: #00feff;
   --bg-color: #072d44;
   --el-text-color-placeholder: #4ba1fa;
-  --el-fill-color-lighter: #1F3C64;
+  --el-fill-color-lighter: #1f3c64;
   --border-light-color: #143a92;
   --filter-input-border-color: rgba(255, 227, 41, 0.2);
   --el-dialog-bg-color: var(--bg-color);
@@ -52,7 +52,7 @@
   --el-tree-node-content-height: 26px;
   --el-tree-node-hover-bg-color: #214865;
   --el-tree-text-color: var(--el-text-color-regular);
-  --el-tree-expand-icon-color: #1FCBE1;
+  --el-tree-expand-icon-color: #1fcbe1;
   background: none;
   color: #ffffff;
   cursor: default;
@@ -75,10 +75,10 @@
   --el-table-header-text-color: #fff;
   --el-table-row-hover-bg-color: #1a5a8b;
   --el-table-current-row-bg-color: var(--el-color-primary-light-9);
-  --el-table-header-bg-color: #1F3C64;
+  --el-table-header-bg-color: #1f3c64;
   --el-table-fixed-box-shadow: var(--el-box-shadow-light);
   --el-table-bg-color: var(--bg-color);
-  --el-table-tr-bg-color: #0D2B4D;
+  --el-table-tr-bg-color: #0d2b4d;
   --el-table-expanded-cell-bg-color: var(--el-fill-color-blank);
   --el-table-index: var(--el-index-normal);
   background-color: var(--bg-color);
@@ -123,10 +123,11 @@
   background: transparent;
 }
 html.blue-theme .ys-title {
-  font-family: 'YouSheBiaoTiHei';
+  font-family: "YouSheBiaoTiHei";
 }
 html.blue-theme .panel-title {
-  font-family: 'YouSheBiaoTiHei';
+  font-family: "YouSheBiaoTiHei";
+  font-size: 24px;
   padding-left: 1.4em;
   background: url("@/assets/images/tb1.png") 6px center / auto 82% no-repeat;
 }
@@ -134,16 +135,16 @@
   color: #1fcbe1;
 }
 .el-tabs--border-card > .el-tabs__header.el-tabs__header {
-  border-bottom: 1px solid #00fefe;
+  border-bottom: 1px solid #1c4975;
   background-color: #00243e;
 }
 .el-tabs--border-card > .el-tabs__header.el-tabs__header .el-tabs__item {
-  color: #00fefe;
+  color: #fff;
 }
 .el-tabs--border-card > .el-tabs__header.el-tabs__header .el-tabs__item.is-active {
-  background-color: #00fefe;
-  border-color: #00fefe;
-  color: #041f6c;
+  background-color: #014886;
+  border-color: #014886;
+  color: #fff;
 }
 .el-pagination__sizes.el-pagination__sizes,
 .el-pagination__total.el-pagination__total {
@@ -151,9 +152,9 @@
   font-size: 16px;
 }
 .el-pagination .el-select__wrapper.el-select__wrapper {
-  background-color: #134BA8;
+  background-color: #134ba8;
   color: #fff;
-  box-shadow: 0 0 0 1px #436DA8 inset;
+  box-shadow: 0 0 0 1px #436da8 inset;
 }
 .el-pagination .el-select__wrapper.el-select__wrapper .el-select__placeholder,
 .el-pagination .el-select__wrapper.el-select__wrapper .el-select__input {
@@ -181,8 +182,8 @@
   color: #fff;
 }
 .el-pagination__jump.el-pagination__jump .el-input__wrapper {
-  background-color: #134BA8;
-  box-shadow: 0 0 0 1px #436DA8 inset;
+  background-color: #134ba8;
+  box-shadow: 0 0 0 1px #436da8 inset;
 }
 .el-pagination__jump.el-pagination__jump .el-input__wrapper .el-input__inner {
   color: #fff;
@@ -203,8 +204,8 @@
 }
 .el-transfer .el-checkbox__input.is-indeterminate .el-checkbox__inner,
 .el-transfer .el-checkbox__input.is-checked .el-checkbox__inner {
-  background-color: #46CAFE;
-  border-color: #46CAFE;
+  background-color: #46cafe;
+  border-color: #46cafe;
 }
 .el-transfer .el-checkbox__input.is-indeterminate .el-checkbox__inner::before,
 .el-transfer .el-checkbox__input.is-checked .el-checkbox__inner::before {
@@ -216,14 +217,14 @@
 }
 .page-filter {
   display: table;
-  border-bottom: 1px solid #304A75;
+  border-bottom: 1px solid #304a75;
   margin-top: 8px;
   padding: 6px;
   margin-bottom: 6px;
 }
 .page-filter .el-select__wrapper.el-select__wrapper {
   background-color: #103047;
-  color: #48A5D0;
+  color: #48a5d0;
   box-shadow: 0 0 0 1px #103047 inset;
 }
 .page-filter .el-select__placeholder {
@@ -231,16 +232,56 @@
 }
 .page-filter .table-row {
   display: table-row;
+  table-layout: fixed;
 }
 .page-filter .table-row .table-cell {
   display: table-cell;
   white-space: nowrap;
-  color: #48A5D0;
+  color: #48a5d0;
+  font-size: 16px;
 }
 .page-filter .table-cell.text-right {
   text-align: right;
   padding-right: 10px;
 }
+.page-filter .el-date-editor .el-input__wrapper {
+  align-items: center;
+  background-color: #103047;
+  background-image: none;
+  border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
+  box-shadow: 0 0 0 1px #103047 inset;
+  cursor: text;
+  display: inline-flex;
+  flex-grow: 1;
+  justify-content: center;
+  padding: 1px 11px;
+}
+.page-filter .el-date-editor .el-input__wrapper .el-input__inner {
+  color: #48a5d0;
+}
+.page-filter .grid-container {
+  display: grid;
+  grid-template-columns: repeat(var(--counter), auto 1fr);
+  gap: 12px;
+  /* 鍒椾箣闂寸殑闂磋窛 */
+  padding: 10px;
+}
+.page-filter .grid-container .grid-item {
+  color: #48a5d0;
+  display: contents;
+  /* 浣块」鐩唴瀹圭洿鎺ュ弬涓庣綉鏍煎竷灞� */
+}
+.page-filter .grid-container .grid-item .label {
+  white-space: nowrap;
+  margin-right: -6px;
+  margin-left: 1em;
+  color: #48a5d0;
+  font-size: 16px;
+  text-align: right;
+}
+.page-filter .grid-container .grid-item .label::after {
+  content: ":";
+}
 .pos-r {
   position: relative;
 }
diff --git a/src/styles/blue.less b/src/styles/blue.less
index 7bbfa30..2992f14 100644
--- a/src/styles/blue.less
+++ b/src/styles/blue.less
@@ -5,7 +5,7 @@
     --bg-color: #072d44;
     --el-text-color-placeholder: #4ba1fa;
     // --el-fill-color-lighter: var(--bg-color);
-    --el-fill-color-lighter: #1F3C64;
+    --el-fill-color-lighter: #1f3c64;
     --border-light-color: #143a92;
     --filter-input-border-color: rgba(255, 227, 41, 0.2);
     --el-dialog-bg-color: var(--bg-color);
@@ -83,7 +83,7 @@
     --el-tree-node-content-height: 26px;
     --el-tree-node-hover-bg-color: #214865;
     --el-tree-text-color: var(--el-text-color-regular);
-    --el-tree-expand-icon-color: #1FCBE1;
+    --el-tree-expand-icon-color: #1fcbe1;
     background: none;
     color: #ffffff;
     cursor: default;
@@ -110,10 +110,10 @@
     --el-table-row-hover-bg-color: #1a5a8b;
     --el-table-current-row-bg-color: var(--el-color-primary-light-9);
     // --el-table-header-bg-color: #021f31;
-    --el-table-header-bg-color: #1F3C64;
+    --el-table-header-bg-color: #1f3c64;
     --el-table-fixed-box-shadow: var(--el-box-shadow-light);
     --el-table-bg-color: var(--bg-color);
-    --el-table-tr-bg-color: #0D2B4D;
+    --el-table-tr-bg-color: #0d2b4d;
     --el-table-expanded-cell-bg-color: var(--el-fill-color-blank);
     --el-table-index: var(--el-index-normal);
     background-color: var(--bg-color);
@@ -155,7 +155,6 @@
     }
   }
 
-
   .page-content {
     padding-bottom: 20px;
     .el-table {
@@ -164,34 +163,35 @@
   }
 
   .ys-title {
-    font-family: 'YouSheBiaoTiHei';
+    font-family: "YouSheBiaoTiHei";
   }
 
   .panel-title {
-    font-family: 'YouSheBiaoTiHei';
+    font-family: "YouSheBiaoTiHei";
+    font-size: 24px;
     padding-left: 1.4em;
-    background: url("@/assets/images/tb1.png") 6px center e('/') auto 82% no-repeat;
+    background: url("@/assets/images/tb1.png") 6px center e("/") auto 82%
+      no-repeat;
   }
 }
-
 
 .el-tree.el-tree.el-tree {
   color: #1fcbe1;
 }
 
 .el-tabs--border-card > .el-tabs__header.el-tabs__header {
-  border-bottom: 1px solid #00fefe;
+  border-bottom: 1px solid #1c4975;
   background-color: #00243e;
 }
 .el-tabs--border-card > .el-tabs__header.el-tabs__header .el-tabs__item {
-  color: #00fefe;
+  color: #fff;
 }
 .el-tabs--border-card
   > .el-tabs__header.el-tabs__header
   .el-tabs__item.is-active {
-  background-color: #00fefe;
-  border-color: #00fefe;
-  color: #041f6c;
+  background-color: #014886;
+  border-color: #014886;
+  color: #fff;
 }
 
 .el-pagination__sizes.el-pagination__sizes,
@@ -201,9 +201,9 @@
 }
 
 .el-pagination .el-select__wrapper.el-select__wrapper {
-  background-color: #134BA8;
+  background-color: #134ba8;
   color: #fff;
-  box-shadow: 0 0 0 1px #436DA8 inset;
+  box-shadow: 0 0 0 1px #436da8 inset;
 }
 .el-pagination .el-select__wrapper.el-select__wrapper .el-select__placeholder,
 .el-pagination .el-select__wrapper.el-select__wrapper .el-select__input {
@@ -221,7 +221,6 @@
   cursor: not-allowed;
 }
 
-
 .el-pagination .el-pager.el-pager.el-pager li {
   background: transparent;
   color: #fff;
@@ -234,14 +233,14 @@
   color: #fff;
 }
 .el-pagination__jump.el-pagination__jump .el-input__wrapper {
-  background-color: #134BA8;
-  box-shadow: 0 0 0 1px #436DA8 inset;
+  background-color: #134ba8;
+  box-shadow: 0 0 0 1px #436da8 inset;
 }
 .el-pagination__jump.el-pagination__jump .el-input__wrapper .el-input__inner {
   color: #fff;
 }
 
-.el-tree-node:focus>.el-tree-node__content.el-tree-node__content {
+.el-tree-node:focus > .el-tree-node__content.el-tree-node__content {
   background: transparent;
 }
 
@@ -254,7 +253,6 @@
   outline: none;
 }
 
-
 .el-tooltip__popper.el-tooltip__popper {
   z-index: 9999; /* 璁剧疆杈冮珮鐨� z-index */
 }
@@ -262,8 +260,8 @@
 .el-transfer {
   .el-checkbox__input.is-indeterminate .el-checkbox__inner,
   .el-checkbox__input.is-checked .el-checkbox__inner {
-    background-color: #46CAFE;
-    border-color: #46CAFE;
+    background-color: #46cafe;
+    border-color: #46cafe;
     &::before {
       background-color: #333;
     }
@@ -273,17 +271,17 @@
   }
 }
 
-// 鎼滅储鏉′欢鏍峰紡 
+// 鎼滅储鏉′欢鏍峰紡
 .page-filter {
   display: table;
-  border-bottom: 1px solid #304A75;
+  border-bottom: 1px solid #304a75;
   margin-top: 8px;
   padding: 6px;
   margin-bottom: 6px;
 
   .el-select__wrapper.el-select__wrapper {
     background-color: #103047;
-    color: #48A5D0;
+    color: #48a5d0;
     box-shadow: 0 0 0 1px #103047 inset;
   }
   .el-select__placeholder {
@@ -291,16 +289,73 @@
   }
 
   .table-row {
-      display: table-row;
+    display: table-row;
+    table-layout: fixed;
   }
   .table-row .table-cell {
-      display: table-cell;
-      white-space: nowrap;
-    color: #48A5D0;
+    display: table-cell;
+    white-space: nowrap;
+    color: #48a5d0;
+    font-size: 16px;
   }
   .table-cell.text-right {
-      text-align: right;
+    text-align: right;
     padding-right: 10px;
+  }
+
+  .el-date-editor {
+    .el-input__wrapper {
+      align-items: center;
+      background-color: #103047;
+      background-image: none;
+      border-radius: var(
+        --el-input-border-radius,
+        var(--el-border-radius-base)
+      );
+      box-shadow: 0 0 0 1px #103047 inset;
+      cursor: text;
+      display: inline-flex;
+      flex-grow: 1;
+      justify-content: center;
+      padding: 1px 11px;
+      .el-input__inner {
+        color: #48a5d0;
+      }
+    }
+  }
+
+  .grid-container {
+    display: grid;
+    grid-template-columns: repeat(var(--counter), auto 1fr);
+    // gap: 15px; /* 鍧囧寑闂撮殧 */
+    // padding: 10px;
+
+    gap: 12px; /* 鍒椾箣闂寸殑闂磋窛 */
+    padding: 10px;
+    .grid-item {
+      color: #48a5d0;
+      //     display: flex;
+      // align-items: center;
+      display: contents; /* 浣块」鐩唴瀹圭洿鎺ュ弬涓庣綉鏍煎竷灞� */
+      .label {
+        white-space: nowrap;
+        // font-weight: bold;
+        margin-right: -6px;
+        margin-left: 1em;
+        color: #48a5d0;
+        font-size: 16px;
+        text-align: right;
+        &::after {
+          content: ":";
+        }
+      }
+      .value {
+        // flex-grow: 1;
+        // overflow: hidden;
+        // text-overflow: ellipsis;
+        // white-space: nowrap;
+      }
+    }
   }
 }
 
@@ -318,7 +373,8 @@
 .page-contain {
   height: 100%;
   &.bg-footer {
-    background: url("@/assets/images/bg_bottom.png") center calc(100% - 20px) e('/') 100% auto no-repeat;
+    background: url("@/assets/images/bg_bottom.png") center calc(100% - 20px)
+      e("/") 100% auto no-repeat;
   }
   .page-main {
     padding-left: 20px;
@@ -330,4 +386,4 @@
 @font-face {
   font-family: "YouSheBiaoTiHei";
   src: url(./YouSheBiaoTiHei-2.ttf) format("truetype");
-}
\ No newline at end of file
+}
diff --git a/src/utils/const/const_digit.js b/src/utils/const/const_digit.js
new file mode 100644
index 0000000..195535c
--- /dev/null
+++ b/src/utils/const/const_digit.js
@@ -0,0 +1,9 @@
+// 鍏ㄥ眬灏忔暟浣嶆暟
+export default {
+  // 鐧惧垎姣�
+  PREC: 2,
+  // 鐢靛帇
+  VOL: 3,
+  // 鐢垫祦
+  CURR: 3,
+}
\ No newline at end of file
diff --git a/src/utils/const/const_hrTestType.js b/src/utils/const/const_hrTestType.js
new file mode 100644
index 0000000..09bab73
--- /dev/null
+++ b/src/utils/const/const_hrTestType.js
@@ -0,0 +1,7 @@
+export default {
+  // TODO
+  1: '鏍稿鏀剧數',
+  2: '鏍稿鍏呯數',
+  3: '鐩戞帶鏀剧數',
+  4: '鐩戞帶鍏呯數'
+}
\ No newline at end of file
diff --git a/src/utils/excel/Blob.js b/src/utils/excel/Blob.js
new file mode 100644
index 0000000..3fa0b67
--- /dev/null
+++ b/src/utils/excel/Blob.js
@@ -0,0 +1,204 @@
+/* eslint-disable */
+/* Blob.js*/
+
+/*global self, unescape */
+/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
+  plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
+
+(function(view) {
+	"use strict";
+
+	view.URL = view.URL || view.webkitURL;
+
+	if (view.Blob && view.URL) {
+		try {
+			new Blob;
+			return;
+		} catch (e) {}
+	}
+
+	// Internally we use a BlobBuilder implementation to base Blob off of
+	// in order to support older browsers that only have BlobBuilder
+	var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
+		var
+			get_class = function(object) {
+				return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
+			},
+			FakeBlobBuilder = function BlobBuilder() {
+				this.data = [];
+			},
+			FakeBlob = function Blob(data, type, encoding) {
+				this.data = data;
+				this.size = data.length;
+				this.type = type;
+				this.encoding = encoding;
+			},
+			FBB_proto = FakeBlobBuilder.prototype,
+			FB_proto = FakeBlob.prototype,
+			FileReaderSync = view.FileReaderSync,
+			FileException = function(type) {
+				this.code = this[this.name = type];
+			},
+			file_ex_codes = (
+				"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " +
+				"NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
+			).split(" "),
+			file_ex_code = file_ex_codes.length,
+			real_URL = view.URL || view.webkitURL || view,
+			real_create_object_URL = real_URL.createObjectURL,
+			real_revoke_object_URL = real_URL.revokeObjectURL,
+			URL = real_URL,
+			btoa = view.btoa,
+			atob = view.atob
+
+			,
+			ArrayBuffer = view.ArrayBuffer,
+			Uint8Array = view.Uint8Array
+
+			,
+			origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/;
+		FakeBlob.fake = FB_proto.fake = true;
+		while (file_ex_code--) {
+			FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
+		}
+		// Polyfill URL
+		if (!real_URL.createObjectURL) {
+			URL = view.URL = function(uri) {
+				var
+					uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a"),
+					uri_origin;
+				uri_info.href = uri;
+				if (!("origin" in uri_info)) {
+					if (uri_info.protocol.toLowerCase() === "data:") {
+						uri_info.origin = null;
+					} else {
+						uri_origin = uri.match(origin);
+						uri_info.origin = uri_origin && uri_origin[1];
+					}
+				}
+				return uri_info;
+			};
+		}
+		URL.createObjectURL = function(blob) {
+			var
+				type = blob.type,
+				data_URI_header;
+			if (type === null) {
+				type = "application/octet-stream";
+			}
+			if (blob instanceof FakeBlob) {
+				data_URI_header = "data:" + type;
+				if (blob.encoding === "base64") {
+					return data_URI_header + ";base64," + blob.data;
+				} else if (blob.encoding === "URI") {
+					return data_URI_header + "," + decodeURIComponent(blob.data);
+				}
+				if (btoa) {
+					return data_URI_header + ";base64," + btoa(blob.data);
+				} else {
+					return data_URI_header + "," + encodeURIComponent(blob.data);
+				}
+			} else if (real_create_object_URL) {
+				return real_create_object_URL.call(real_URL, blob);
+			}
+		};
+		URL.revokeObjectURL = function(object_URL) {
+			if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
+				real_revoke_object_URL.call(real_URL, object_URL);
+			}
+		};
+		FBB_proto.append = function(data /*, endings*/ ) {
+			var bb = this.data;
+			// decode data to a binary string
+			if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
+				var
+					str = "",
+					buf = new Uint8Array(data),
+					i = 0,
+					buf_len = buf.length;
+				for (; i < buf_len; i++) {
+					str += String.fromCharCode(buf[i]);
+				}
+				bb.push(str);
+			} else if (get_class(data) === "Blob" || get_class(data) === "File") {
+				if (FileReaderSync) {
+					var fr = new FileReaderSync;
+					bb.push(fr.readAsBinaryString(data));
+				} else {
+					// async FileReader won't work as BlobBuilder is sync
+					throw new FileException("NOT_READABLE_ERR");
+				}
+			} else if (data instanceof FakeBlob) {
+				if (data.encoding === "base64" && atob) {
+					bb.push(atob(data.data));
+				} else if (data.encoding === "URI") {
+					bb.push(decodeURIComponent(data.data));
+				} else if (data.encoding === "raw") {
+					bb.push(data.data);
+				}
+			} else {
+				if (typeof data !== "string") {
+					data += ""; // convert unsupported types to strings
+				}
+				// decode UTF-16 to binary string
+				bb.push(unescape(encodeURIComponent(data)));
+			}
+		};
+		FBB_proto.getBlob = function(type) {
+			if (!arguments.length) {
+				type = null;
+			}
+			return new FakeBlob(this.data.join(""), type, "raw");
+		};
+		FBB_proto.toString = function() {
+			return "[object BlobBuilder]";
+		};
+		FB_proto.slice = function(start, end, type) {
+			var args = arguments.length;
+			if (args < 3) {
+				type = null;
+			}
+			return new FakeBlob(
+				this.data.slice(start, args > 1 ? end : this.data.length), type, this.encoding
+			);
+		};
+		FB_proto.toString = function() {
+			return "[object Blob]";
+		};
+		FB_proto.close = function() {
+			this.size = 0;
+			delete this.data;
+		};
+		return FakeBlobBuilder;
+	}(view));
+
+	view.Blob = function(blobParts, options) {
+		var type = options ? (options.type || "") : "";
+		var builder = new BlobBuilder();
+		if (blobParts) {
+			for (var i = 0, len = blobParts.length; i < len; i++) {
+				if (Uint8Array && blobParts[i] instanceof Uint8Array) {
+					builder.append(blobParts[i].buffer);
+				} else {
+					builder.append(blobParts[i]);
+				}
+			}
+		}
+		var blob = builder.getBlob(type);
+		if (!blob.slice && blob.webkitSlice) {
+			blob.slice = blob.webkitSlice;
+		}
+		return blob;
+	};
+
+	var getPrototypeOf = Object.getPrototypeOf || function(object) {
+		return object.__proto__;
+	};
+	view.Blob.prototype = getPrototypeOf(new view.Blob());
+}(
+	typeof self !== "undefined" && self ||
+	typeof window !== "undefined" && window ||
+	this
+));
diff --git a/src/utils/excel/Export2Excel.js b/src/utils/excel/Export2Excel.js
new file mode 100644
index 0000000..73c2fae
--- /dev/null
+++ b/src/utils/excel/Export2Excel.js
@@ -0,0 +1,179 @@
+/* eslint-disable */
+require('script-loader!file-saver');
+require('./Blob.js')
+require('script-loader!xlsx/dist/xlsx.core.min');
+
+function generateArray(table) {
+	var out = [];
+	var rows = table.querySelectorAll('tr');
+	var ranges = [];
+	for (var R = 0; R < rows.length; ++R) {
+		var outRow = [];
+		var row = rows[R];
+		var columns = row.querySelectorAll('td');
+		for (var C = 0; C < columns.length; ++C) {
+			var cell = columns[C];
+			var colspan = cell.getAttribute('colspan');
+			var rowspan = cell.getAttribute('rowspan');
+			var cellValue = cell.innerText;
+			if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
+
+			//Skip ranges
+			ranges.forEach(function(range) {
+				if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
+					for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
+				}
+			});
+
+			//Handle Row Span
+			if (rowspan || colspan) {
+				rowspan = rowspan || 1;
+				colspan = colspan || 1;
+				ranges.push({
+					s: {
+						r: R,
+						c: outRow.length
+					},
+					e: {
+						r: R + rowspan - 1,
+						c: outRow.length + colspan - 1
+					}
+				});
+			};
+
+			//Handle Value
+			outRow.push(cellValue !== "" ? cellValue : null);
+
+			//Handle Colspan
+			if (colspan)
+				for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
+		}
+		out.push(outRow);
+	}
+	return [out, ranges];
+};
+
+function datenum(v, date1904) {
+	if (date1904) v += 1462;
+	var epoch = Date.parse(v);
+	return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
+}
+
+function sheet_from_array_of_arrays(data, opts) {
+	var ws = {};
+	var range = {
+		s: {
+			c: 10000000,
+			r: 10000000
+		},
+		e: {
+			c: 0,
+			r: 0
+		}
+	};
+	for (var R = 0; R != data.length; ++R) {
+		for (var C = 0; C != data[R].length; ++C) {
+			if (range.s.r > R) range.s.r = R;
+			if (range.s.c > C) range.s.c = C;
+			if (range.e.r < R) range.e.r = R;
+			if (range.e.c < C) range.e.c = C;
+			var cell = {
+				v: data[R][C]
+			};
+			if (cell.v == null) continue;
+			var cell_ref = XLSX.utils.encode_cell({
+				c: C,
+				r: R
+			});
+
+			if (typeof cell.v === 'number') cell.t = 'n';
+			else if (typeof cell.v === 'boolean') cell.t = 'b';
+			else if (cell.v instanceof Date) {
+				cell.t = 'n';
+				cell.z = XLSX.SSF._table[14];
+				cell.v = datenum(cell.v);
+			} else cell.t = 's';
+
+			ws[cell_ref] = cell;
+		}
+	}
+	if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
+	return ws;
+}
+
+function Workbook() {
+	if (!(this instanceof Workbook)) return new Workbook();
+	this.SheetNames = [];
+	this.Sheets = {};
+}
+
+function s2ab(s) {
+	var buf = new ArrayBuffer(s.length);
+	var view = new Uint8Array(buf);
+	for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
+	return buf;
+}
+
+export function export_table_to_excel(id) {
+	var theTable = document.getElementById(id);
+	console.log('a')
+	var oo = generateArray(theTable);
+	var ranges = oo[1];
+
+	/* original data */
+	var data = oo[0];
+	var ws_name = "SheetJS";
+	console.log(data);
+
+	var wb = new Workbook(),
+		ws = sheet_from_array_of_arrays(data);
+
+	/* add ranges to worksheet */
+	// ws['!cols'] = ['apple', 'banan'];
+	ws['!merges'] = ranges;
+
+	/* add worksheet to workbook */
+	wb.SheetNames.push(ws_name);
+	wb.Sheets[ws_name] = ws;
+
+	var wbout = XLSX.write(wb, {
+		bookType: 'xlsx',
+		bookSST: false,
+		type: 'binary'
+	});
+
+	saveAs(new Blob([s2ab(wbout)], {
+		type: "application/octet-stream"
+	}), "test.xlsx")
+}
+
+function formatJson(jsonData) {
+	console.log(jsonData)
+}
+
+export function export_json_to_excel(th, jsonData, defaultTitle) {
+
+	/* original data */
+
+	var data = jsonData;
+	data.unshift(th);
+	var ws_name = "SheetJS";
+
+	var wb = new Workbook(),
+		ws = sheet_from_array_of_arrays(data);
+
+
+	/* add worksheet to workbook */
+	wb.SheetNames.push(ws_name);
+	wb.Sheets[ws_name] = ws;
+
+	var wbout = XLSX.write(wb, {
+		bookType: 'xlsx',
+		bookSST: false,
+		type: 'binary'
+	});
+	var title = defaultTitle || '鍒楄〃'
+	saveAs(new Blob([s2ab(wbout)], {
+		type: "application/octet-stream"
+	}), title + ".xlsx")
+}
diff --git a/src/utils/export2Excel.js b/src/utils/export2Excel.js
new file mode 100644
index 0000000..d3302ec
--- /dev/null
+++ b/src/utils/export2Excel.js
@@ -0,0 +1,147 @@
+import { saveAs } from "file-saver";
+import * as XLSX from "xlsx";
+/**
+ *
+ * @param {Object} workbook 宸ヤ綔钖�
+ * @param {Object} worksheet 宸ヤ綔琛�
+ * @param {Object} cell 鍗曞厓鏍�
+ * 鏍囪锛屽紩鐢ㄥ崟鍏冩牸鏃舵墍浣跨敤鐨勫湴鍧�鏍煎紡锛堝锛欰1銆丆7锛�
+ */
+function datenum(v, date1904) {
+  if (date1904) v += 1462;
+  var epoch = Date.parse(v);
+  return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
+}
+function sheetFromArrayOfArrays(data, opts) {
+  var ws = {};
+  var range = {
+    s: {
+      c: 10000000,
+      r: 10000000,
+    },
+    e: {
+      c: 0,
+      r: 0,
+    },
+  };
+  for (var R = 0; R !== data.length; ++R) {
+    for (var C = 0; C !== data[R].length; ++C) {
+      if (range.s.r > R) range.s.r = R;
+      if (range.s.c > C) range.s.c = C;
+      if (range.e.r < R) range.e.r = R;
+      if (range.e.c < C) range.e.c = C;
+      var cell = {
+        v: data[R][C], // v琛ㄧず鍗曞厓鏍煎師濮嬪�硷紝 t琛ㄧず鍐呭绫诲瀷锛宻-string绫诲瀷锛宯-number绫诲瀷锛宐-boolean绫诲瀷锛宒-date绫诲瀷锛岀瓑绛�
+      };
+      if (cell.v == null) continue;
+      /**
+       * 閫氳繃鍦板潃瀵硅薄 { r: R, c: C } 鏉ヨ幏鍙栧崟鍏冩牸锛孯 鍜� C 鍒嗗埆浠h〃浠� 0 寮�濮嬬殑琛屽拰鍒楃殑绱㈠紩銆�
+       * XLSX.utils 涓殑 encode_cell/decode_cell 鏂规硶鍙互杞崲鍗曞厓鏍煎湴鍧�
+       *    XLSX.utils.encode_cell({ r: 7, c: 2 })  ===銆� C7
+       */
+      var cellRef = XLSX.utils.encode_cell({ c: C, r: R });
+      if (typeof cell.v === "number") cell.t = "n";
+      else if (typeof cell.v === "boolean") cell.t = "b";
+      else if (cell.v instanceof Date) {
+        cell.t = "n";
+        cell.z = XLSX.SSF._table[14];
+        cell.v = datenum(cell.v);
+      } else cell.t = "s";
+      ws[cellRef] = cell;
+    }
+  }
+  // ws['!ref']锛氳〃绀烘墍鏈夊崟鍏冩牸鐨勮寖鍥达紝渚嬪浠嶢1鍒癋8鍒欒褰曚负A1:F8
+  if (range.s.c < 10000000) ws["!ref"] = XLSX.utils.encode_range(range);
+  return ws;
+}
+function Workbook() {
+  if (!(this instanceof Workbook)) return new Workbook();
+  this.SheetNames = [];
+  this.Sheets = {};
+}
+// 瀛楃涓茶浆涓篈rrayBuffer
+function s2ab(s) {
+  var buf = new ArrayBuffer(s.length);
+  var view = new Uint8Array(buf);
+  for (var i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
+  return buf;
+}
+/**
+ *
+ * @param {Array} multiHeader  澶氳琛ㄥご
+ * @param {Array} header  琛ㄥご
+ * @param {Array} data  鏁版嵁
+ * @param {String} filename  鏂囦欢鍚�
+ * @param {Array} merges  鍚堝苟鍗曞厓鏍�
+ * @param {Boolean} autoWidth  鏄惁璁剧疆鍗曞厓鏍煎搴�
+ * @param {String} bookType  瑕佺敓鎴愮殑鏂囦欢绫诲瀷
+ */
+export function exportJsonToExcel({
+  multiHeader = [],
+  header,
+  data,
+  filename,
+  merges = [],
+  autoWidth = true,
+  bookType = "xlsx",
+} = {}) {
+  filename = filename || "excel-list";
+  data = [...data];
+  data.unshift(header);
+  for (let i = multiHeader.length - 1; i > -1; i--) {
+    data.unshift(multiHeader[i]);
+  }
+  var wsName = "SheetJS";
+  var wb = new Workbook();
+  var ws = sheetFromArrayOfArrays(data);
+  if (merges.length > 0) {
+    // ws[!merges]锛氬瓨鏀句竴浜涘崟鍏冩牸鍚堝苟淇℃伅锛屾槸涓�涓暟缁勶紝姣忎釜鏁扮粍鐢卞寘鍚玸鍜宔鏋勬垚鐨勫璞$粍鎴愶紝s琛ㄧず寮�濮嬶紝e琛ㄧず缁撴潫锛宺琛ㄧず琛岋紝c琛ㄧず鍒�
+    if (!ws["!merges"]) ws["!merges"] = [];
+    merges.forEach((item) => {
+      ws["!merges"].push(XLSX.utils.decode_range(item));
+    });
+  }
+  if (autoWidth) {
+    /* 璁剧疆worksheet姣忓垪鐨勬渶澶у搴� */
+    const colWidth = data.map((row) =>
+      row.map((val) => {
+        /* 鍏堝垽鏂槸鍚︿负null/undefined */
+        if (val == null) {
+          return { wch: 10 };
+        } else if (val.toString().charCodeAt(0) > 255) {
+          /* 鍐嶅垽鏂槸鍚︿负涓枃 */
+          return {
+            wch: val.toString().length * 2,
+          };
+        } else {
+          return {
+            wch: val.toString().length,
+          };
+        }
+      })
+    );
+    /* 浠ョ涓�琛屼负鍒濆鍊� */
+    let result = colWidth[0];
+    for (let i = 1; i < colWidth.length; i++) {
+      for (let j = 0; j < colWidth[i].length; j++) {
+        if (result[j] && result[j]["wch"] < colWidth[i][j]["wch"]) {
+          result[j]["wch"] = colWidth[i][j]["wch"];
+        }
+      }
+    }
+    // ws['!cols']璁剧疆鍗曞厓鏍煎搴�, [{'wch': 10},{'wch': 10}] ===> 绗竴鍒楀拰绗簩鍒楄缃簡瀹藉害
+    ws["!cols"] = result;
+  }
+  /* add worksheet to workbook */
+  wb.SheetNames.push(wsName);
+  wb.Sheets[wsName] = ws;
+  var wbout = XLSX.write(wb, {
+    bookType: bookType,
+    bookSST: false, // 鏄惁鐢熸垚Shared String Table锛屽畼鏂硅В閲婃槸锛屽鏋滃紑鍚敓鎴愰�熷害浼氫笅闄嶏紝浣嗗湪浣庣増鏈琁OS璁惧涓婃湁鏇村ソ鐨勫吋瀹规��
+    type: "binary",
+  });
+  saveAs(
+    new Blob([s2ab(wbout)], { type: "application/octet-stream" }),
+    `${filename}.${bookType}`
+  );
+}
diff --git a/src/utils/exportFile.js b/src/utils/exportFile.js
new file mode 100644
index 0000000..d0247bc
--- /dev/null
+++ b/src/utils/exportFile.js
@@ -0,0 +1,19 @@
+import { exportJsonToExcel } from "./export2Excel";
+
+function ExportFile(headers, list, name) {
+    let tHeader = [];
+    let filterVal = [];
+    headers.map((item, index) => {
+        tHeader.push(item.label);
+        filterVal.push(item.prop);
+    });
+    let excelData = formatJson(filterVal, list);
+
+    exportJsonToExcel({header: tHeader, data: excelData, filename: name});
+}
+
+function formatJson(filterVal, jsonData) {
+    return jsonData.map(v => filterVal.map(j => v[j]));
+}
+
+export { ExportFile };
\ No newline at end of file
diff --git a/src/utils/getQueryString.js b/src/utils/getQueryString.js
new file mode 100644
index 0000000..057054b
--- /dev/null
+++ b/src/utils/getQueryString.js
@@ -0,0 +1,11 @@
+//鑾峰彇杩炴帴涓殑鎸囧畾鍙傛暟
+function getQueryString(name) { 
+    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); 
+    var r = window.location.href.replace(/^.*\?/, '').match(reg); 
+    if (r != null) {
+        return decodeURI(r[2]);
+    }	 
+    return null;
+}
+
+export default getQueryString;
\ No newline at end of file
diff --git a/src/utils/getTreeDataByKey.js b/src/utils/getTreeDataByKey.js
new file mode 100644
index 0000000..c7af637
--- /dev/null
+++ b/src/utils/getTreeDataByKey.js
@@ -0,0 +1,36 @@
+/**
+ * 鏍规嵁key鍊艰幏鍙栨爲鐘惰妭鐐圭殑瀵硅薄
+ *
+ * @param   {[String]}  key       鏌ヨ鐨勫��
+ * @param   {[Array]}   treeData   鏍戠姸鏁版嵁
+ * @param   {[String]}  nodeKey   瀵规瘮鐨勫睘鎬у悕锛岄粯璁や负id
+ * @param   {[String]}  childKey  瀛愯妭鐐圭殑灞炴�у悕 榛樿涓篶hildren
+ * @return  {[Object]}            杩斿洖key鎵�鍦ㄧ殑鐨勫璞� -1琛ㄧず鏈幏鍙栧埌
+ */
+function getTreeDataByKey(key, treeData, nodeKey, childKey) {
+    let id = nodeKey?nodeKey:'id';
+    let child = childKey?childKey:'children';
+    // 妫�娴嬫暟鎹笉鏄暟缁勶紙涓柇鍥炶皟鍑芥暟锛�
+    if(!(treeData instanceof Array)) {
+        return -1;
+    }
+    let result = -1;
+    // 閬嶅巻鏍戠姸鏁版嵁鑾峰彇涓庡彲浠ュ搴旂殑瀵硅薄
+    for(let i=0; i<treeData.length; i++) {
+        let item = treeData[i];
+        if(item[id] == key) {
+            result = item;
+            break;
+        }else {
+            result = getTreeDataByKey(key, item[child], nodeKey, childKey);
+            if(result != -1) {
+                break;
+            }
+        }
+    }
+
+    return result;
+}
+
+export default getTreeDataByKey;
+
diff --git a/src/utils/toFixed.js b/src/utils/toFixed.js
new file mode 100644
index 0000000..d947af2
--- /dev/null
+++ b/src/utils/toFixed.js
@@ -0,0 +1,16 @@
+import digits from './const/const_digit';
+
+function isNumeric(str) {
+  return !isNaN(Number(str));
+}
+
+function toFixed(value, bit) {
+  if (!isNumeric(value) || !isNumeric(bit)) return value;
+  const num = Math.pow(10, bit);
+  return Math.round(value * num) / num;
+}
+
+export {
+  digits,
+  toFixed
+};
\ No newline at end of file
diff --git a/src/views/alarm/battAlarm.vue b/src/views/alarm/battAlarm.vue
new file mode 100644
index 0000000..081c54e
--- /dev/null
+++ b/src/views/alarm/battAlarm.vue
@@ -0,0 +1,501 @@
+<script setup name="battAlarm">
+	import { ref, reactive, onMounted, computed, nextTick, watch, } from "vue";
+	import { storeToRefs } from "pinia";
+	import { Search, Plus } from "@element-plus/icons-vue";
+	import ycCard from "@/components/ycCard/index.vue";
+	// import addEdit from "./addEdit.vue";
+	import { ElMessage } from "element-plus";
+	import useElement from "@/hooks/useElement.js";
+  import { useUserStore } from '@/store/user';
+
+  import useStation from "@/hooks/useStationList.js";
+  const { provice, city, country, stationName,
+    proviceList, cityList, countryList, stationList,
+  } = useStation();
+
+  import powerTypes from '@/utils/const/const_powerType.js';
+  import { ExportFile } from '@/utils/exportFile.js';
+  import { useRouter } from "vue-router";
+  const router = useRouter();
+
+  const userStore = useUserStore();
+  const { uid, uname } = storeToRefs(userStore);
+
+  import useWebSocket from "@/hooks/useWebSocket.js";
+  const { message, sendData } = useWebSocket("battAlmReal");
+
+
+  import {
+    confirmBattAlm,
+  } from "@/api/alarm.js";
+
+	const { $loading, $message, $confirm } = useElement();
+
+  const alarmLevel = ref();
+  const alarmLevels = [
+    {
+      label: '涓�绾у憡璀�',
+      value: 1,
+    },
+    {
+      label: '浜岀骇鍛婅',
+      value: 2,
+    },
+    {
+      label: '涓夌骇鍛婅',
+      value: 3,
+    },
+    {
+      label: '鍥涚骇鍛婅',
+      value: 4,
+    },
+  ];
+
+const headers = [
+    // {
+		// 	prop: "provice",
+		// 	label: "鐪�",
+		// 	width: "80",
+		// },
+    // {
+		// 	prop: "city",
+		// 	label: "甯�",
+		// 	width: "80",
+		// },
+    // {
+		// 	prop: "country",
+		// 	label: "鍖哄幙",
+		// 	width: "80",
+		// },
+		{
+			prop: "stationName",
+			label: "鏈烘埧鍚嶇О",
+			width: "160",
+		},
+    // {
+		// 	prop: "stationType",
+		// 	label: "鐢靛帇绛夌骇",
+		// 	width: "80",
+		// },
+    {
+			prop: "battgroupName",
+			label: "鐢垫睜缁勫悕绉�",
+			width: "120",
+		},
+    {
+			prop: "almId",
+			label: "鍛婅绫诲瀷",
+			width: "120",
+		},
+    {
+			prop: "almValue",
+			label: "鍛婅鍊�",
+			width: "120",
+		},
+    {
+			prop: "monNum",
+			label: "鍗曚綋缂栧彿",
+			width: "120",
+		},
+    {
+			prop: "almLevelStr",
+			label: "鍛婅绛夌骇",
+			width: "120",
+		},
+    {
+			prop: "almStartTime",
+			label: "鍛婅寮�濮嬫椂闂�",
+			width: "120",
+		},
+    {
+			prop: "almIsConfirmed",
+			label: "鍛婅鏄惁纭",
+			width: "120",
+		},
+    {
+      prop: "almConfirmedTime",
+      label: "鍛婅纭鏃堕棿",
+      width: "120",
+    },
+	];
+	
+	const background = ref(true);
+	const disabled = ref(false);
+	const pageCurr = ref(1);
+	const pageSize = ref(10);
+	const total = ref(0);
+	const addEditVisible = ref(false);
+	const dialogTitle = ref("");
+	const currentAreaId = ref();
+	const currentAreaIds = ref([]);
+	const datas = reactive({
+		tableData: [],
+		rowData: {},
+	});
+
+	// const tableData = reactive([]);
+	// const rowData = reactive({});
+
+	// const userStore = useUserStore();
+	// const { uid, uname } = storeToRefs(userStore);
+
+  // almIds: [119001],
+  // almLevel: "1",
+  // city: "姝︽眽甯�",
+  // country: "涓滆タ婀栧尯",
+  // pageNum: 1,
+  // pageSize: 10,
+  // provice: "婀栧寳鐪�",
+  // stationName: "娴嬭瘯鏈烘埧6",
+  function sendMessage() {
+    let params = {
+      // almIds: [119001],
+      almLevel: alarmLevel.value || undefined,
+      provice: provice.value || undefined,
+      city: city.value || undefined,
+      country: country.value || undefined,
+      stationName: stationName.value || undefined,
+      pageNum: pageCurr.value,
+      pageSize: pageSize.value,
+    };
+    sendData(JSON.stringify(params));
+  }
+
+  function selectChange() {
+    nextTick(() => {
+      sendMessage();
+    });
+  }
+
+  watch(
+    () => message.value,
+    (n) => {
+      if (n) {
+        let {code, data, data2} = JSON.parse(n);
+        let list = [];
+				let _total = 0;
+				if (code && data) {
+					// console.log(data);
+					list = data2.list.map(v => ({
+            ...v,
+            almLevelStr: ['', '涓�绾у憡璀�', '浜岀骇鍛婅', '涓夌骇鍛婅', '鍥涚骇鍛婅'][v.almLevel],
+            almIsConfirmedStr: ['鏈‘璁�', '宸茬‘璁�'][v.almIsConfirmed],
+            // roleName: roles[v.role],
+          }));
+					_total = data2.total;
+				}
+				datas.tableData = list;
+				total.value = _total;
+      }
+    }
+  );
+
+
+	// 灞曠ず鏁版嵁鏁伴噺
+	function handleSizeChange(val) {
+		pageSize.value = val;
+		sendMessage();
+	}
+	// 缈婚〉
+	function handleCurrentChange(val) {
+		pageCurr.value = val;
+		sendMessage();
+	}
+	
+  function confirmAlarm(record) {
+    $confirm("纭鍛婅", () => {
+      let loading = $loading();
+      confirmBattAlm(record.num)
+        .then((res) => {
+          let { code, data } = res;
+          loading.close();
+          if (code && data) {
+            $message.success("鎿嶄綔鎴愬姛");
+            sendMessage();
+          } else {
+            $message.success("鎿嶄綔澶辫触");
+          }
+        })
+        .catch((err) => {
+          loading.close();
+          console.log(err);
+        });
+    });
+  }
+  
+	function onOk() {
+		addEditVisible.value = false;
+		handleCurrentChange(1);
+	}
+	function onCanel() {
+		addEditVisible.value = false;
+	}
+
+  function exportExcel() {
+    let _headers = headers.map(v => {
+      let prop = v.prop;
+      let label = v.label;
+      if (prop == 'almIsConfirmed') {
+        prop = 'almIsConfirmedStr';
+      }
+      return {
+        prop,
+        label
+      };
+    });
+    ExportFile(_headers, datas.tableData, "鐢垫睜瀹炴椂鍛婅");
+  }
+
+  function goRt (row) {
+    router.push({
+      path: '/datas/realtime',
+      query: {
+        id: row.battgroupId
+      }
+    });
+  }
+
+
+	onMounted(() => {
+		sendMessage();
+	});
+</script>
+
+<template>
+  <div class="page-wrapper">
+    <!-- <div class="page-header">
+    </div> -->
+    <div class="page-content">
+      <yc-card is-full>
+        <div class="page-content-wrapper">
+          <div class="page-content-tools page-filter">
+            <div class="table-row">
+              <div class="table-cell text-right">鐪侊細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="provice"
+                  size="small"
+                  clearable
+                  placeholder="璇烽�夋嫨鐪�"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in proviceList"
+                    :key="'l0_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">甯傦細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="city"
+                  size="small"
+                  clearable
+                  placeholder="璇烽�夋嫨甯�"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in cityList"
+                    :key="'l1_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鍖哄幙:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="country"
+                  clearable
+                  size="small"
+                  placeholder="璇烽�夋嫨鍖哄幙"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in countryList"
+                    :key="'l2_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">绔欑偣:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="stationName"
+                  clearable
+                  size="small"
+                  placeholder="璇烽�夋嫨绔欑偣"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in stationList"
+                    :key="'l3_' + item.stationId"
+                    :label="item.stationName"
+                    :value="item.stationName"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鍛婅绛夌骇:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="alarmLevel"
+                  clearable
+                  size="small"
+                  placeholder="璇烽�夋嫨鍛婅绛夌骇"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in alarmLevels"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-table">
+            <div class="pos-rel">
+              <div class="pos-abs">
+                <el-table class="yc-table" stripe height="100%" size="small" :data="datas.tableData" style="width: 100%">
+                  <el-table-column type="index" fixed="left" label="搴忓彿" width="60"></el-table-column>
+                  <el-table-column v-for="header in headers" :key="header.prop" :prop="header.prop" :label="header.label"
+                    :min-width="header.width" align="center">
+                    <template #default="scope">
+                      <template v-if="header.prop == 'almIsConfirmed'">
+                        <el-checkbox disabled :checked="scope.row[header.prop] == 1"></el-checkbox>
+                      </template>
+                      <template v-else>
+                        {{ scope.row[header.prop] || '--' }}
+                      </template>
+                    </template>  
+                  </el-table-column>
+                  <el-table-column label="鎿嶄綔" fixed="right" width="240" align="center">
+                    <template #default="scope">
+                      <el-button type="primary" size="small" v-if="!scope.row.almIsConfirmed"
+                        @click="confirmAlarm(scope.row)">纭鍛婅</el-button>
+                      <el-button type="warning" size="small" @click="goRt(scope.row)">瀹炴椂鐩戞祴</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-page">
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="sendMessage" :icon="Search">鏌ヨ</el-button>
+            </div>
+            <div class="el-page-container">
+              <el-pagination v-model:current-page="pageCurr" v-model:page-size="pageSize"
+                :page-sizes="[20, 40, 60, 80, 100, 200, 300, 400]" size="small" :disabled="disabled"
+                :background="background" layout="total, sizes, prev, pager, next, jumper" :total="total"
+                @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+            </div>
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="exportExcel" >瀵煎嚭</el-button>
+            </div>
+          </div>
+        </div>
+      </yc-card>
+    </div>
+    <div class="page-footer"></div>
+    <!-- 寮圭獥 -->
+    <!-- <el-dialog :title="dialogTitle" v-model="addEditVisible" top="0" :close-on-click-modal="false" class="dialog-center"
+      width="860px" center>
+      <add-edit v-if="addEditVisible" @success="onOk" :info="datas.rowData" @cancel="onCanel"></add-edit>
+    </el-dialog> -->
+  </div>
+</template>
+
+<style scoped lang="less">
+.page-wrapper {
+  display: flex;
+  flex-direction: row;
+  // padding: 8px;
+  height: 100%;
+
+  .page-content {
+    flex: 1;
+  }
+}
+
+.page-content-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .page-content-tools {
+    padding-bottom: 8px;
+  }
+
+  .page-content-table {
+    // border-top: 1px solid var(--border-light-color);
+    box-sizing: border-box;
+    flex: 1;
+    margin-left: 26px;
+    margin-right: 26px;
+  }
+
+  .page-content-page {
+    padding: 8px 8px 0 8px;
+    text-align: center;
+
+    .el-page-container {
+      display: inline-block;
+      padding: 0 16px;
+    }
+
+    .page-tool {
+      display: inline-block;
+    }
+  }
+}
+
+.hdw-card-container {
+  width: 240px;
+  padding-right: 8px;
+  height: 100%;
+}
+
+.pos-rel {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .pos-abs {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.tools-filter {
+  display: inline-block;
+  font-size: 14px;
+
+  .tools-filter-item {
+    display: inline-block;
+    margin-right: 8px;
+
+    .filter-label {
+      display: inline-block;
+    }
+
+    .filter-content {
+      display: inline-block;
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/alarm/devAlarm.vue b/src/views/alarm/devAlarm.vue
new file mode 100644
index 0000000..268b4f2
--- /dev/null
+++ b/src/views/alarm/devAlarm.vue
@@ -0,0 +1,495 @@
+<script setup name="devAlarm">
+	import { ref, reactive, onMounted, computed, nextTick, watch, } from "vue";
+	import { storeToRefs } from "pinia";
+	import { Search, Plus } from "@element-plus/icons-vue";
+	import ycCard from "@/components/ycCard/index.vue";
+	// import addEdit from "./addEdit.vue";
+	import { ElMessage } from "element-plus";
+	import useElement from "@/hooks/useElement.js";
+  import { useUserStore } from '@/store/user';
+
+  import useStation from "@/hooks/useStationList.js";
+  const { provice, city, country, stationName,
+    proviceList, cityList, countryList, stationList,
+  } = useStation();
+
+  import powerTypes from '@/utils/const/const_powerType.js';
+  import { ExportFile } from '@/utils/exportFile.js';
+  import { useRouter } from "vue-router";
+  const router = useRouter();
+
+  const userStore = useUserStore();
+  const { uid, uname } = storeToRefs(userStore);
+
+  import useWebSocket from "@/hooks/useWebSocket.js";
+  const { message, sendData } = useWebSocket("devAlmReal");
+
+  import {
+    confirmDevAlm,
+  } from "@/api/alarm.js";
+
+	const { $loading, $message, $confirm } = useElement();
+
+  const alarmLevel = ref();
+  const alarmLevels = [
+    {
+      label: '涓�绾у憡璀�',
+      value: 1,
+    },
+    {
+      label: '浜岀骇鍛婅',
+      value: 2,
+    },
+    {
+      label: '涓夌骇鍛婅',
+      value: 3,
+    },
+    {
+      label: '鍥涚骇鍛婅',
+      value: 4,
+    },
+  ];
+
+const headers = [
+    // {
+		// 	prop: "provice",
+		// 	label: "鐪�",
+		// 	width: "80",
+		// },
+    // {
+		// 	prop: "city",
+		// 	label: "甯�",
+		// 	width: "80",
+		// },
+    // {
+		// 	prop: "country",
+		// 	label: "鍖哄幙",
+		// 	width: "80",
+		// },
+		{
+			prop: "fullName",
+			label: "鏈烘埧鍚嶇О",
+			width: "160",
+		},
+    // {
+		// 	prop: "stationType",
+		// 	label: "鐢靛帇绛夌骇",
+		// 	width: "80",
+		// },
+    {
+			prop: "devName",
+			label: "璁惧鍚嶇О",
+			width: "120",
+		},
+    {
+			prop: "almId",
+			label: "鍛婅绫诲瀷",
+			width: "120",
+		},
+    {
+			prop: "almValue",
+			label: "鍛婅鍊�",
+			width: "120",
+		},
+    {
+			prop: "almLevelStr",
+			label: "鍛婅绛夌骇",
+			width: "120",
+		},
+    {
+			prop: "almStartTime",
+			label: "鍛婅寮�濮嬫椂闂�",
+			width: "120",
+		},
+    {
+			prop: "almIsConfirmed",
+			label: "鍛婅鏄惁纭",
+			width: "120",
+		},
+    {
+      prop: "almConfirmedTime",
+      label: "鍛婅纭鏃堕棿",
+      width: "120",
+    },
+	];
+	
+	const background = ref(true);
+	const disabled = ref(false);
+	const pageCurr = ref(1);
+	const pageSize = ref(10);
+	const total = ref(0);
+	const addEditVisible = ref(false);
+	const dialogTitle = ref("");
+	const currentAreaId = ref();
+	const currentAreaIds = ref([]);
+	const datas = reactive({
+		tableData: [],
+		rowData: {},
+	});
+
+	// const tableData = reactive([]);
+	// const rowData = reactive({});
+
+	// const userStore = useUserStore();
+	// const { uid, uname } = storeToRefs(userStore);
+
+  // almIds: [119001],
+  // almLevel: "1",
+  // city: "姝︽眽甯�",
+  // country: "涓滆タ婀栧尯",
+  // pageNum: 1,
+  // pageSize: 10,
+  // provice: "婀栧寳鐪�",
+  // stationName: "娴嬭瘯鏈烘埧6",
+  function sendMessage() {
+    let params = {
+      // almIds: [119001],
+      almLevel: alarmLevel.value || undefined,
+      provice: provice.value || undefined,
+      city: city.value || undefined,
+      country: country.value || undefined,
+      stationName: stationName.value || undefined,
+      pageNum: pageCurr.value,
+      pageSize: pageSize.value,
+    };
+    sendData(JSON.stringify(params));
+  }
+
+  function selectChange() {
+    nextTick(() => {
+      sendMessage();
+    });
+  }
+
+  watch(
+    () => message.value,
+    (n) => {
+      if (n) {
+        let {code, data, data2} = JSON.parse(n);
+        let list = [];
+				let _total = 0;
+				if (code && data) {
+					// console.log(data);
+					list = data2.list.map(v => ({
+            ...v,
+            almLevelStr: ['', '涓�绾у憡璀�', '浜岀骇鍛婅', '涓夌骇鍛婅', '鍥涚骇鍛婅'][v.almLevel],
+            almIsConfirmedStr: ['鏈‘璁�', '宸茬‘璁�'][v.almIsConfirmed],
+            // roleName: roles[v.role],
+          }));
+					_total = data2.total;
+				}
+				datas.tableData = list;
+				total.value = _total;
+      }
+    }
+  );
+
+
+	// 灞曠ず鏁版嵁鏁伴噺
+	function handleSizeChange(val) {
+		pageSize.value = val;
+		sendMessage();
+	}
+	// 缈婚〉
+	function handleCurrentChange(val) {
+		pageCurr.value = val;
+		sendMessage();
+	}
+	
+  function confirmAlarm(record) {
+    $confirm("纭鍛婅", () => {
+      let loading = $loading();
+      confirmDevAlm(record.num)
+        .then((res) => {
+          let { code, data } = res;
+          loading.close();
+          if (code && data) {
+            $message.success("鎿嶄綔鎴愬姛");
+            sendMessage();
+          } else {
+            $message.success("鎿嶄綔澶辫触");
+          }
+        })
+        .catch((err) => {
+          loading.close();
+          console.log(err);
+        });
+    });
+  }
+  
+	function onOk() {
+		addEditVisible.value = false;
+		handleCurrentChange(1);
+	}
+	function onCanel() {
+		addEditVisible.value = false;
+	}
+
+  function exportExcel() {
+    let _headers = headers.map(v => {
+      let prop = v.prop;
+      let label = v.label;
+      if (prop == 'almIsConfirmed') {
+        prop = 'almIsConfirmedStr';
+      }
+      return {
+        prop,
+        label
+      };
+    });
+    ExportFile(_headers, datas.tableData, "鐢垫睜瀹炴椂鍛婅");
+  }
+
+  function goRt (row) {
+    router.push({
+      path: '/datas/realtime',
+      query: {
+        id: row.battgroupId
+      }
+    });
+  }
+
+
+	onMounted(() => {
+		sendMessage();
+	});
+</script>
+
+<template>
+  <div class="page-wrapper">
+    <!-- <div class="page-header">
+    </div> -->
+    <div class="page-content">
+      <yc-card is-full>
+        <div class="page-content-wrapper">
+          <div class="page-content-tools page-filter">
+            <div class="table-row">
+              <div class="table-cell text-right">鐪侊細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="provice"
+                  size="small"
+                  clearable
+                  placeholder="璇烽�夋嫨鐪�"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in proviceList"
+                    :key="'l0_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">甯傦細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="city"
+                  size="small"
+                  clearable
+                  placeholder="璇烽�夋嫨甯�"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in cityList"
+                    :key="'l1_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鍖哄幙:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="country"
+                  clearable
+                  size="small"
+                  placeholder="璇烽�夋嫨鍖哄幙"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in countryList"
+                    :key="'l2_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">绔欑偣:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="stationName"
+                  clearable
+                  size="small"
+                  placeholder="璇烽�夋嫨绔欑偣"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in stationList"
+                    :key="'l3_' + item.stationId"
+                    :label="item.stationName"
+                    :value="item.stationName"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鍛婅绛夌骇:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="alarmLevel"
+                  clearable
+                  size="small"
+                  placeholder="璇烽�夋嫨鍛婅绛夌骇"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in alarmLevels"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-table">
+            <div class="pos-rel">
+              <div class="pos-abs">
+                <el-table class="yc-table" stripe height="100%" size="small" :data="datas.tableData" style="width: 100%">
+                  <el-table-column type="index" fixed="left" label="搴忓彿" width="60"></el-table-column>
+                  <el-table-column v-for="header in headers" :key="header.prop" :prop="header.prop" :label="header.label"
+                    :min-width="header.width" align="center">
+                    <template #default="scope">
+                      <template v-if="header.prop == 'almIsConfirmed'">
+                        <el-checkbox disabled :checked="scope.row[header.prop] == 1"></el-checkbox>
+                      </template>
+                      <template v-else>
+                        {{ scope.row[header.prop] || '--' }}
+                      </template>
+                    </template>  
+                  </el-table-column>
+                  <el-table-column label="鎿嶄綔" fixed="right" width="240" align="center">
+                    <template #default="scope">
+                      <el-button type="primary" size="small" v-if="!scope.row.almIsConfirmed"
+                        @click="confirmAlarm(scope.row)">纭鍛婅</el-button>
+                      <el-button type="warning" size="small" @click="goRt(scope.row)">瀹炴椂鐩戞祴</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-page">
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="sendMessage" :icon="Search">鏌ヨ</el-button>
+            </div>
+            <div class="el-page-container">
+              <el-pagination v-model:current-page="pageCurr" v-model:page-size="pageSize"
+                :page-sizes="[20, 40, 60, 80, 100, 200, 300, 400]" size="small" :disabled="disabled"
+                :background="background" layout="total, sizes, prev, pager, next, jumper" :total="total"
+                @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+            </div>
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="exportExcel" >瀵煎嚭</el-button>
+            </div>
+          </div>
+        </div>
+      </yc-card>
+    </div>
+    <div class="page-footer"></div>
+    <!-- 寮圭獥 -->
+    <!-- <el-dialog :title="dialogTitle" v-model="addEditVisible" top="0" :close-on-click-modal="false" class="dialog-center"
+      width="860px" center>
+      <add-edit v-if="addEditVisible" @success="onOk" :info="datas.rowData" @cancel="onCanel"></add-edit>
+    </el-dialog> -->
+  </div>
+</template>
+
+<style scoped lang="less">
+.page-wrapper {
+  display: flex;
+  flex-direction: row;
+  // padding: 8px;
+  height: 100%;
+
+  .page-content {
+    flex: 1;
+  }
+}
+
+.page-content-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .page-content-tools {
+    padding-bottom: 8px;
+  }
+
+  .page-content-table {
+    // border-top: 1px solid var(--border-light-color);
+    box-sizing: border-box;
+    flex: 1;
+    margin-left: 26px;
+    margin-right: 26px;
+  }
+
+  .page-content-page {
+    padding: 8px 8px 0 8px;
+    text-align: center;
+
+    .el-page-container {
+      display: inline-block;
+      padding: 0 16px;
+    }
+
+    .page-tool {
+      display: inline-block;
+    }
+  }
+}
+
+.hdw-card-container {
+  width: 240px;
+  padding-right: 8px;
+  height: 100%;
+}
+
+.pos-rel {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .pos-abs {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.tools-filter {
+  display: inline-block;
+  font-size: 14px;
+
+  .tools-filter-item {
+    display: inline-block;
+    margin-right: 8px;
+
+    .filter-label {
+      display: inline-block;
+    }
+
+    .filter-content {
+      display: inline-block;
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/alarm/pwrAlarm.vue b/src/views/alarm/pwrAlarm.vue
new file mode 100644
index 0000000..a830a18
--- /dev/null
+++ b/src/views/alarm/pwrAlarm.vue
@@ -0,0 +1,503 @@
+<script setup name="pwrAlarm">
+	import { ref, reactive, onMounted, computed, nextTick, watch, } from "vue";
+	import { storeToRefs } from "pinia";
+	import { Search, Plus } from "@element-plus/icons-vue";
+	import ycCard from "@/components/ycCard/index.vue";
+	// import addEdit from "./addEdit.vue";
+	import { ElMessage } from "element-plus";
+	import useElement from "@/hooks/useElement.js";
+  import { useUserStore } from '@/store/user';
+
+  import useStation from "@/hooks/useStationList.js";
+  const { provice, city, country, stationName,
+    proviceList, cityList, countryList, stationList,
+  } = useStation();
+
+  import powerTypes from '@/utils/const/const_powerType.js';
+  import { ExportFile } from '@/utils/exportFile.js';
+  import { useRouter } from "vue-router";
+  const router = useRouter();
+
+  const userStore = useUserStore();
+  const { uid, uname } = storeToRefs(userStore);
+
+  import useWebSocket from "@/hooks/useWebSocket.js";
+  const { message, sendData } = useWebSocket("pwrAlmReal");
+
+	import {
+		getAllUser,
+		deleteUser,
+		dropRole,
+		improveRole,
+		resetSnId,
+	} from "@/api/user";
+
+  import {
+    confirmPwrAlm,
+  } from "@/api/alarm.js";
+
+	const { $loading, $message, $confirm } = useElement();
+
+  const alarmLevel = ref();
+  const alarmLevels = [
+    {
+      label: '涓�绾у憡璀�',
+      value: 1,
+    },
+    {
+      label: '浜岀骇鍛婅',
+      value: 2,
+    },
+    {
+      label: '涓夌骇鍛婅',
+      value: 3,
+    },
+    {
+      label: '鍥涚骇鍛婅',
+      value: 4,
+    },
+  ];
+
+const headers = [
+    // {
+		// 	prop: "provice",
+		// 	label: "鐪�",
+		// 	width: "80",
+		// },
+    // {
+		// 	prop: "city",
+		// 	label: "甯�",
+		// 	width: "80",
+		// },
+    // {
+		// 	prop: "country",
+		// 	label: "鍖哄幙",
+		// 	width: "80",
+		// },
+		{
+			prop: "fullName",
+			label: "鏈烘埧鍚嶇О",
+			width: "160",
+		},
+    // {
+		// 	prop: "stationType",
+		// 	label: "鐢靛帇绛夌骇",
+		// 	width: "80",
+		// },
+    {
+			prop: "powerName",
+			label: "鐢垫簮鍚嶇О",
+			width: "120",
+		},
+    {
+			prop: "almId",
+			label: "鍛婅绫诲瀷",
+			width: "120",
+		},
+    {
+			prop: "almValue",
+			label: "鍛婅鍊�",
+			width: "120",
+		},
+    {
+			prop: "almLevelStr",
+			label: "鍛婅绛夌骇",
+			width: "120",
+		},
+    {
+			prop: "almStartTime",
+			label: "鍛婅寮�濮嬫椂闂�",
+			width: "120",
+		},
+    {
+			prop: "almIsConfirmed",
+			label: "鍛婅鏄惁纭",
+			width: "120",
+		},
+    {
+      prop: "almConfirmedTime",
+      label: "鍛婅纭鏃堕棿",
+      width: "120",
+    },
+	];
+	
+	const background = ref(true);
+	const disabled = ref(false);
+	const pageCurr = ref(1);
+	const pageSize = ref(10);
+	const total = ref(0);
+	const addEditVisible = ref(false);
+	const dialogTitle = ref("");
+	const currentAreaId = ref();
+	const currentAreaIds = ref([]);
+	const datas = reactive({
+		tableData: [],
+		rowData: {},
+	});
+
+	// const tableData = reactive([]);
+	// const rowData = reactive({});
+
+	// const userStore = useUserStore();
+	// const { uid, uname } = storeToRefs(userStore);
+
+  // almIds: [119001],
+  // almLevel: "1",
+  // city: "姝︽眽甯�",
+  // country: "涓滆タ婀栧尯",
+  // pageNum: 1,
+  // pageSize: 10,
+  // provice: "婀栧寳鐪�",
+  // stationName: "娴嬭瘯鏈烘埧6",
+  function sendMessage() {
+    let params = {
+      // almIds: [119001],
+      almLevel: alarmLevel.value || undefined,
+      provice: provice.value || undefined,
+      city: city.value || undefined,
+      country: country.value || undefined,
+      stationName: stationName.value || undefined,
+      pageNum: pageCurr.value,
+      pageSize: pageSize.value,
+    };
+    sendData(JSON.stringify(params));
+  }
+
+  function selectChange() {
+    nextTick(() => {
+      sendMessage();
+    });
+  }
+
+  watch(
+    () => message.value,
+    (n) => {
+      if (n) {
+        let {code, data, data2} = JSON.parse(n);
+        let list = [];
+				let _total = 0;
+				if (code && data) {
+					// console.log(data);
+					list = data2.list.map(v => ({
+            ...v,
+            almLevelStr: ['', '涓�绾у憡璀�', '浜岀骇鍛婅', '涓夌骇鍛婅', '鍥涚骇鍛婅'][v.almLevel],
+            almIsConfirmedStr: ['鏈‘璁�', '宸茬‘璁�'][v.almIsConfirmed],
+            // roleName: roles[v.role],
+          }));
+					_total = data2.total;
+				}
+				datas.tableData = list;
+				total.value = _total;
+      }
+    }
+  );
+
+
+	// 灞曠ず鏁版嵁鏁伴噺
+	function handleSizeChange(val) {
+		pageSize.value = val;
+		sendMessage();
+	}
+	// 缈婚〉
+	function handleCurrentChange(val) {
+		pageCurr.value = val;
+		sendMessage();
+	}
+	
+  function confirmAlarm(record) {
+    $confirm("纭鍛婅", () => {
+      let loading = $loading();
+      confirmPwrAlm(record.num)
+        .then((res) => {
+          let { code, data } = res;
+          loading.close();
+          if (code && data) {
+            $message.success("鎿嶄綔鎴愬姛");
+            sendMessage();
+          } else {
+            $message.success("鎿嶄綔澶辫触");
+          }
+        })
+        .catch((err) => {
+          loading.close();
+          console.log(err);
+        });
+    });
+  }
+  
+	function onOk() {
+		addEditVisible.value = false;
+		handleCurrentChange(1);
+	}
+	function onCanel() {
+		addEditVisible.value = false;
+	}
+
+  function exportExcel() {
+    let _headers = headers.map(v => {
+      let prop = v.prop;
+      let label = v.label;
+      if (prop == 'almIsConfirmed') {
+        prop = 'almIsConfirmedStr';
+      }
+      return {
+        prop,
+        label
+      };
+    });
+    ExportFile(_headers, datas.tableData, "鐢垫睜瀹炴椂鍛婅");
+  }
+
+  function goRt (row) {
+    router.push({
+      path: '/datas/realtime',
+      query: {
+        id: row.battgroupId
+      }
+    });
+  }
+
+
+	onMounted(() => {
+		sendMessage();
+	});
+</script>
+
+<template>
+  <div class="page-wrapper">
+    <!-- <div class="page-header">
+    </div> -->
+    <div class="page-content">
+      <yc-card is-full>
+        <div class="page-content-wrapper">
+          <div class="page-content-tools page-filter">
+            <div class="table-row">
+              <div class="table-cell text-right">鐪侊細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="provice"
+                  size="small"
+                  clearable
+                  placeholder="璇烽�夋嫨鐪�"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in proviceList"
+                    :key="'l0_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">甯傦細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="city"
+                  size="small"
+                  clearable
+                  placeholder="璇烽�夋嫨甯�"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in cityList"
+                    :key="'l1_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鍖哄幙:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="country"
+                  clearable
+                  size="small"
+                  placeholder="璇烽�夋嫨鍖哄幙"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in countryList"
+                    :key="'l2_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">绔欑偣:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="stationName"
+                  clearable
+                  size="small"
+                  placeholder="璇烽�夋嫨绔欑偣"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in stationList"
+                    :key="'l3_' + item.stationId"
+                    :label="item.stationName"
+                    :value="item.stationName"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鍛婅绛夌骇:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="alarmLevel"
+                  clearable
+                  size="small"
+                  placeholder="璇烽�夋嫨鍛婅绛夌骇"
+                  @change="selectChange"
+                >
+                  <el-option
+                    v-for="item in alarmLevels"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-table">
+            <div class="pos-rel">
+              <div class="pos-abs">
+                <el-table class="yc-table" stripe height="100%" size="small" :data="datas.tableData" style="width: 100%">
+                  <el-table-column type="index" fixed="left" label="搴忓彿" width="60"></el-table-column>
+                  <el-table-column v-for="header in headers" :key="header.prop" :prop="header.prop" :label="header.label"
+                    :min-width="header.width" align="center">
+                    <template #default="scope">
+                      <template v-if="header.prop == 'almIsConfirmed'">
+                        <el-checkbox disabled :checked="scope.row[header.prop] == 1"></el-checkbox>
+                      </template>
+                      <template v-else>
+                        {{ scope.row[header.prop] || '--' }}
+                      </template>
+                    </template>  
+                  </el-table-column>
+                  <el-table-column label="鎿嶄綔" fixed="right" width="240" align="center">
+                    <template #default="scope">
+                      <el-button type="primary" size="small" v-if="!scope.row.almIsConfirmed"
+                        @click="confirmAlarm(scope.row)">纭鍛婅</el-button>
+                      <el-button type="warning" size="small" @click="goRt(scope.row)">瀹炴椂鐩戞祴</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-page">
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="sendMessage" :icon="Search">鏌ヨ</el-button>
+            </div>
+            <div class="el-page-container">
+              <el-pagination v-model:current-page="pageCurr" v-model:page-size="pageSize"
+                :page-sizes="[20, 40, 60, 80, 100, 200, 300, 400]" size="small" :disabled="disabled"
+                :background="background" layout="total, sizes, prev, pager, next, jumper" :total="total"
+                @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+            </div>
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="exportExcel" >瀵煎嚭</el-button>
+            </div>
+          </div>
+        </div>
+      </yc-card>
+    </div>
+    <div class="page-footer"></div>
+    <!-- 寮圭獥 -->
+    <!-- <el-dialog :title="dialogTitle" v-model="addEditVisible" top="0" :close-on-click-modal="false" class="dialog-center"
+      width="860px" center>
+      <add-edit v-if="addEditVisible" @success="onOk" :info="datas.rowData" @cancel="onCanel"></add-edit>
+    </el-dialog> -->
+  </div>
+</template>
+
+<style scoped lang="less">
+.page-wrapper {
+  display: flex;
+  flex-direction: row;
+  // padding: 8px;
+  height: 100%;
+
+  .page-content {
+    flex: 1;
+  }
+}
+
+.page-content-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .page-content-tools {
+    padding-bottom: 8px;
+  }
+
+  .page-content-table {
+    // border-top: 1px solid var(--border-light-color);
+    box-sizing: border-box;
+    flex: 1;
+    margin-left: 26px;
+    margin-right: 26px;
+  }
+
+  .page-content-page {
+    padding: 8px 8px 0 8px;
+    text-align: center;
+
+    .el-page-container {
+      display: inline-block;
+      padding: 0 16px;
+    }
+
+    .page-tool {
+      display: inline-block;
+    }
+  }
+}
+
+.hdw-card-container {
+  width: 240px;
+  padding-right: 8px;
+  height: 100%;
+}
+
+.pos-rel {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .pos-abs {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.tools-filter {
+  display: inline-block;
+  font-size: 14px;
+
+  .tools-filter-item {
+    display: inline-block;
+    margin-right: 8px;
+
+    .filter-label {
+      display: inline-block;
+    }
+
+    .filter-content {
+      display: inline-block;
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/datas/addEdit.vue b/src/views/datas/addEdit.vue
index e01751d..77df8df 100644
--- a/src/views/datas/addEdit.vue
+++ b/src/views/datas/addEdit.vue
@@ -448,7 +448,7 @@
         form1[key] = info[key];
       }
 
-      addBinfFlag.value = !!info.battgroupId * 1;
+      addBinfFlag.value = info.stationId ? !!info.battgroupId * 1 : 1;
       if (info.addBattFlag) {
         if (!info.devId) {
           addDevFlag.value = 1;
diff --git a/src/views/datas/device.vue b/src/views/datas/device.vue
index 306e0bf..75b4786 100644
--- a/src/views/datas/device.vue
+++ b/src/views/datas/device.vue
@@ -1,5 +1,5 @@
-<script setup name="UserManage">
-	import { ref, reactive, onMounted, computed } from "vue";
+<script setup name="device">
+	import { ref, reactive, onMounted, computed, nextTick } from "vue";
 	import { storeToRefs } from "pinia";
 	import { Search, Plus } from "@element-plus/icons-vue";
 	import ycCard from "@/components/ycCard/index.vue";
@@ -14,17 +14,16 @@
   } = useStation();
 
   import powerTypes from '@/utils/const/const_powerType.js';
+  import {
+    delBatt,
+    getPowerBrand,
+    getVoltageLevel,
+  } from "@/api/station";
 
   const userStore = useUserStore();
   const { uid, uname } = storeToRefs(userStore);
 
-	import {
-		getAllUser,
-		deleteUser,
-		dropRole,
-		improveRole,
-		resetSnId,
-	} from "@/api/user";
+
 
   import {
     getDevList,
@@ -149,6 +148,12 @@
 			width: "120",
 		},
 	];
+
+  const company = ref("");
+  const companyList = ref([]);
+  const volLevels = ref([]);
+  const powerType = ref('');
+  const stationType = ref('');
 	const background = ref(true);
 	const disabled = ref(false);
 	const pageCurr = ref(1);
@@ -163,6 +168,10 @@
 		rowData: {},
 	});
 
+  const powerTypeList = computed(() => {
+    return Object.keys(powerTypes).map(v => ({ label: powerTypes[v], value: v * 1 }));
+  });
+
 	// const tableData = reactive([]);
 	// const rowData = reactive({});
 
@@ -172,13 +181,15 @@
 	function getList() {
 		let loading = $loading();
     let params = {
-      // city: "",
-      // country: "",
+      provice: provice.value || undefined,
+      city: city.value || undefined,
+      country: country.value || undefined,
+      stationName: stationName.value || undefined,
+      powerType: powerType.value || undefined,
+      stationType: stationType.value || undefined,
       pageNum: pageCurr.value,
       pageSize: pageSize.value,
       // powerName: "",
-      // provice: "",
-      // stationName: "",
     };
 
 		getDevList(params)
@@ -207,6 +218,30 @@
 			});
 	}
 
+  function getCompanyList() {
+    getPowerBrand().then((res) => {
+      let { code, data, data2 } = res;
+      let list = [];
+      if (code && data) {
+        list = data2;
+      }
+      companyList.value = list;
+    });
+  }
+
+  // 鑾峰彇鐢靛帇绛夌骇
+  function getVolLevels() {
+    console.log("鑾峰彇鐢靛帇绛夌骇");
+    getVoltageLevel().then((res) => {
+      let { code, data, data2 } = res;
+      let list = [];
+      if (code && data) {
+        list = data2;
+      }
+      volLevels.value = list;
+    });
+  }
+
 	// 灞曠ず鏁版嵁鏁伴噺
 	function handleSizeChange(val) {
 		pageSize.value = val;
@@ -219,7 +254,7 @@
 	}
 	function add() {
 		dialogTitle.value = "娣诲姞璁惧";
-		datas.rowData = null;
+		datas.rowData = {};
 		addEditVisible.value = true;
 	}
 	function edit(record) {
@@ -241,13 +276,14 @@
   }
   
 	function confirmRemove(record) {
-		$confirm("鍒犻櫎璇ョ敤鎴�", () => {
-			remove(record.name);
+		$confirm("鍒犻櫎", () => {
+      let { stationId, powerId, battgroupId } = record;
+			remove(stationId, powerId, battgroupId||undefined);
 		});
 	}
-	function remove(uname) {
+	function remove(stationId, powerId, battgroupId) {
 		let loading = $loading();
-		deleteUser(uname)
+		delBatt(stationId, powerId, battgroupId)
 			.then((res) => {
 				let { code, data } = res;
 				loading.close();
@@ -270,59 +306,18 @@
 	function onCanel() {
 		addEditVisible.value = false;
 	}
-	function improveRolefn(record) {
-		let loading = $loading();
-		improveRole(record.id)
-			.then((res) => {
-				let { code, data, msg } = res;
-				loading.close();
-				if (code && data) {
-					$message.success(msg);
-					getList();
-				} else {
-					$message.error(msg);
-				}
-			})
-			.catch((err) => {
-				console.log(err);
-				loading.close();
-			});
-	}
-	function dropRolefn(record) {
-		let loading = $loading();
-		dropRole(record.id)
-			.then((res) => {
-				let { code, data, msg } = res;
-				loading.close();
-				if (code && data) {
-					$message.success(msg);
-					getList();
-				} else {
-					$message.error(msg);
-				}
-			})
-			.catch((err) => {
-				loading.close();
-				console.log(err);
-			});
-	}
-	function resetSnIdfn(record) {
-		$confirm("閲嶇疆璇ョ敤鎴峰瘑鐮�", () => {
-			let loading = $loading();
-			resetSnId(record.id).then((res) => {
-				let { code, data, msg } = res;
-				if (code && data) {
-					$message.success(msg);
-				} else {
-					$message.error(msg);
-				}
-				loading.close();
-			});
-		});
-	}
+
+  function filterChange() {
+    nextTick(() => {
+      pageCurr.value = 1;
+      getList();
+    });
+  }
 
 
 	onMounted(() => {
+    getCompanyList();
+    getVolLevels();
 		getList();
 	});
 </script>
@@ -341,6 +336,8 @@
                 <el-select
                   v-model="provice"
                   size="small"
+                  clearable
+                  @change="filterChange"
                   placeholder="璇烽�夋嫨鐪�"
                 >
                   <el-option
@@ -357,6 +354,8 @@
                 <el-select
                   v-model="city"
                   size="small"
+                  clearable
+                  @change="filterChange"
                   placeholder="璇烽�夋嫨甯�"
                 >
                   <el-option
@@ -373,6 +372,8 @@
                 <el-select
                   v-model="country"
                   size="small"
+                  clearable
+                  @change="filterChange"
                   placeholder="璇烽�夋嫨鍖哄幙"
                 >
                   <el-option
@@ -389,6 +390,8 @@
                 <el-select
                   v-model="stationName"
                   size="small"
+                  clearable
+                  @change="filterChange"
                   placeholder="璇烽�夋嫨绔欑偣"
                 >
                   <el-option
@@ -400,16 +403,36 @@
                   </el-option>
                 </el-select>
               </div>
-              <div class="table-cell text-right">鍝佺墝:</div>
+              <!-- <div class="table-cell text-right">鍝佺墝:</div>
               <div class="table-cell">
                 <el-select
-                  v-model="country"
+                  v-model="company"
                   size="small"
+                  clearable
+                  @change="filterChange"
                   placeholder="璇烽�夋嫨鍝佺墝"
                 >
                   <el-option
-                    v-for="item in countryList"
-                    :key="item.value"
+                    v-for="(item, idx) in companyList"
+                    :key="'list4_' + idx"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div> -->
+              <div class="table-cell text-right">鐢垫簮绫诲瀷:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="powerType"
+                  size="small"
+                  clearable
+                  @change="filterChange"
+                  placeholder="璇烽�夋嫨鍝佺墝"
+                >
+                  <el-option
+                    v-for="(item, idx) in powerTypeList"
+                    :key="'list4_' + idx"
                     :label="item.label"
                     :value="item.value"
                   >
@@ -419,15 +442,17 @@
               <div class="table-cell text-right">鐢靛帇绛夌骇:</div>
               <div class="table-cell">
                 <el-select
-                  v-model="country"
+                  v-model="stationType"
                   size="small"
+                  clearable
+                  @change="filterChange"
                   placeholder="璇烽�夋嫨鐢靛帇绛夌骇"
                 >
                   <el-option
-                    v-for="item in countryList"
-                    :key="item.value"
-                    :label="item.label"
-                    :value="item.value"
+                    v-for="(item, idx) in volLevels"
+                    :key="'list5_' + idx"
+                    :label="item"
+                    :value="item"
                   >
                   </el-option>
                 </el-select>
@@ -445,11 +470,11 @@
                   </el-table-column>
                   <el-table-column label="鎿嶄綔" fixed="right" width="240" align="center">
                     <template #default="scope">
-                      <el-button type="primary" size="small" :disabled="scope.row.name == uname"
+                      <el-button type="primary" size="small"
                         @click="edit(scope.row)">缂栬緫</el-button>
-                      <el-button type="danger" size="small" :disabled="scope.row.name == uname"
+                      <el-button type="danger" size="small" 
                         @click="confirmRemove(scope.row)">鍒犻櫎</el-button>
-                      <el-button type="primary" size="small" :disabled="scope.row.name == uname"
+                      <el-button type="primary" size="small" 
                         @click="addBatt(scope.row)">娣诲姞鐢垫睜缁�</el-button>
                     </template>
                   </el-table-column>
diff --git a/src/views/login/index.vue b/src/views/login/index.vue
index 8e365e8..25d1150 100644
--- a/src/views/login/index.vue
+++ b/src/views/login/index.vue
@@ -145,6 +145,11 @@
 	password: '',
   verify: ''
 });
+
+// TODO
+loginForm.username = 'hw';
+loginForm.password = '123456';
+
 const loginRules = reactive({
 	username: [{ required: true, trigger: 'blur', validator: validateUsername }],
 	password: [{ required: true, trigger: 'blur', validator: validatePassword }]
diff --git a/src/views/realtime/bar8.vue b/src/views/realtime/bar8.vue
new file mode 100644
index 0000000..d57e788
--- /dev/null
+++ b/src/views/realtime/bar8.vue
@@ -0,0 +1,170 @@
+<script setup>
+	import { onMounted, ref, watchEffect, nextTick, onBeforeUnmount } from "vue";
+	import * as echarts from 'echarts';
+
+
+
+
+	function getOptions() {
+		let option = {
+			backgroundColor: '#001037',
+			grid: {
+				top: '10%',
+				left: '5%',
+				right: '2%',
+				bottom: '14%',
+			},
+			tooltip: {
+				show: false,
+			},
+			xAxis: {
+				data: ['1鏈�', '2鏈�', '3鏈�', '4鏈�', '5鏈�', '6鏈�', '7鏈�', '8鏈�', '9鏈�', '10鏈�', '11鏈�', '12鏈�'],
+				axisLine: {
+					lineStyle: {
+						color: 'transparent', //搴曢儴杈规棰滆壊
+					},
+				},
+				axisLabel: {
+					textStyle: {
+						color: '#fff', //搴曢儴鏂囧瓧棰滆壊
+						fontSize: 12,
+					},
+				},
+			},
+			yAxis: [
+				{
+					type: 'value',
+					splitLine: {
+						show: true,
+						lineStyle: {
+							color: 'rgba(255,255,255,0.2)', //缃戞牸绾跨殑棰滆壊
+							width: 1,
+							type: 'solid',
+						},
+					},
+					axisLine: {
+						show: false,
+						lineStyle: {
+							color: 'transparent', //宸﹁竟妗嗛鑹�
+						},
+					},
+					axisLabel: {
+						show: true,
+						fontSize: 12,
+						textStyle: {
+							color: '#ADD6FF', //宸︽枃瀛楅鑹�
+						},
+					},
+				},
+			],
+			series: [
+				{
+					name: '姣曚笟瀛﹀憳',
+					type: 'bar',
+					barWidth: 30,
+					showBackground: true,
+					backgroundStyle: {
+						color: 'rgba(21,136,209,0.1)',
+						// color: '#f00',
+					},
+					itemStyle: {
+						normal: {
+							color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+								{
+									offset: 0,
+									// color: '#00FFE3',//娓愬彉1
+									color: 'rgba(21,136,209,1)', //娓愬彉1
+									// color: '#0f0', //娓愬彉1
+								},
+								{
+									offset: 1,
+									// color: '#4693EC',//娓愬彉2
+									color: 'rgba(21,136,209,1)', //娓愬彉2
+									// color: '#0f0', //娓愬彉2
+								},
+							]),
+						},
+					},
+					data: [20, 80, 100, 40, 34, 90, 60, 20, 80, 100, 40, 34],
+					z: 0,
+					zlevel: 0,
+				},
+				{
+					type: 'pictorialBar',
+					barWidth: 30,
+					itemStyle: {
+						normal: {
+							color: 'rgba(0,63,119,1)', //鏁版嵁鐨勯棿闅旈鑹�
+							// color: '#f00', //鏁版嵁鐨勯棿闅旈鑹�
+						},
+					},
+					symbolRepeat: 'fixed',
+					symbolMargin: 3,
+					symbol: 'rect',
+					symbolSize: [30, 4],
+					symbolPosition: 'end',
+					symbolOffset: [0, 0],
+					data: [20, 80, 100, 40, 34, 90, 60, 20, 80, 100, 40, 34],
+					z: 1,
+					zlevel: 0,
+				},
+			],
+		};
+
+		return option;
+	}
+
+	let myChart;
+	const chartContainer = ref();
+
+	function initChart() {
+		if (chartContainer.value) {
+			myChart = echarts.init(chartContainer.value, "custom", {
+				rendererOptions: {
+					eventListenerOptions: {
+						passive: true // 鍚敤琚姩浜嬩欢鐩戝惉鍣�
+					}
+				}
+			});
+
+
+			let option = getOptions();
+			myChart.setOption(option);
+
+			// 鐩戝惉绐楀彛鍙樺寲閲嶆柊娓叉煋鍥捐〃
+			window.addEventListener("resize", () => {
+				myChart.resize();
+			});
+		}
+	}
+
+	function updateChart(xLabels, datas) {
+		let option = getOptions(xLabels, datas);
+		myChart.setOption(option);
+	}
+
+	onMounted(() => {
+		initChart();
+	});
+</script>
+
+<template>
+  <div class="chart-wraper">
+    <div class="chart" ref="chartContainer"></div>
+  </div>
+</template>
+
+<style scoped lang="less">
+.chart-wraper {
+  height: 100%;
+  position: relative;
+
+  .chart {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+  }
+}
+</style>
diff --git a/src/views/realtime/index.vue b/src/views/realtime/index.vue
index 0b04e2f..a4943e1 100644
--- a/src/views/realtime/index.vue
+++ b/src/views/realtime/index.vue
@@ -1,23 +1,62 @@
-<script setup>
-import { ref, reactive } from "vue";
+<script setup name="realtime">
+import { ref, reactive, watch, onMounted } from "vue";
 import tabPower from "./tabs/power.vue";
 import tabSystem from "./tabs/system.vue";
+import tabVol from './tabs/vol.vue';
+import siteList from "@/components/siteList/index.vue";
+import  getQueryString  from "@/utils/getQueryString";
+import formatSeconds from '@/utils/formatSeconds';
+
+import { useRoute, useRouter } from "vue-router";
+import useWebsocket from "@/hooks/useWebsocket";
+
+const route = useRoute();
+const router = useRouter();
+
+const { message, sendData } = useWebsocket('real');
+
+const curStationId = ref(
+  getQueryString("stationId") || ''
+);
+
+const curPowerId = ref(
+  getQueryString("powerId") || ''
+);
+
+const curBattgroupId = ref(
+  getQueryString("battgroupId") || ''
+);
+
+const curDevId = ref(
+  getQueryString("devId") || ''
+);
+
+const rtData = reactive({
+  system: {},
+  power: {},
+  vol: {},
+  res: {},
+  tmp: {},
+  threeD: {},
+  self: {},
+  manage: {},
+});
 
 const statusList = reactive([
-	{ label: '绯荤粺鐘舵��', value: '鍏呯數' },
-	{ label: '璁惧鐘舵��', value: '姝e父' },
-	{ label: '鐢垫簮鐘舵��', value: '姝e父' },
-	{ label: '鐢垫睜鐘舵��', value: '姝e父' },
-	{ label: '姣嶇嚎鐢靛帇', value: 60 },
-	{ label: '鍦ㄧ嚎鐢靛帇', value: 60 },
-	{ label: '缁勭鐢靛帇', value: 60 },
-	{ label: '鐢垫睜鐢垫祦', value: 60 },
-	{ label: '娴嬭瘯鏃堕暱', value: 60 },
-	{ label: '娴嬭瘯瀹归噺', value: 60 },
-	{ label: '棰勪及鍓╀綑瀹归噺', value: 60 },
-	{ label: '棰勪及鍓╀綑缁埅', value: 60 },
-	{ label: '鍛婅', value: '鏃�' },
-	{ label: '鏇存柊鏃ユ湡', value: '2022-01-01' },
+	{ label: '绯荤粺鐘舵��', prop: 'systemState', unit: '' },
+	{ label: '璁惧鐘舵��', prop: 'devState', unit: '' },
+	{ label: '鐢垫簮鐘舵��', prop: 'pwrState', unit: '' },
+	{ label: '鐢垫睜鐘舵��', prop: 'battState', unit: '' },
+	{ label: '姣嶇嚎鐢靛帇', prop: 'vbusVol', unit: 'V' },
+	{ label: '鍦ㄧ嚎鐢靛帇', prop: 'onlineVol', unit: 'V' },
+	{ label: '缁勭鐢靛帇', prop: 'captestGroupvol', unit: 'V' },
+	{ label: '鐢垫睜鐢垫祦', prop: 'captestCurr', unit: 'A' },
+	{ label: '娴嬭瘯鏃堕暱', prop: 'captestTimelong', unit: '' },
+	{ label: '娴嬭瘯瀹归噺', prop: 'captestCap', unit: 'Ah' },
+	{ label: '棰勪及鍓╀綑瀹归噺', prop: 'restCap', unit: 'Ah' },
+	{ label: '棰勪及鍓╀綑缁埅', prop: 'restTime', unit: '' },
+	{ label: '鍛婅', prop: 'allALmNum', unit: '' },
+	{ label: '鏇存柊鏃ユ湡', prop: 'recordtime', unit: '' },
 ]);
 
 const tabs = ref([
@@ -25,10 +64,13 @@
 	{ label: '鐢垫簮', name: 'power' },
 	{ label: '鐢靛帇', name: 'vol' },
 	{ label: '鍐呴樆', name: 'res' },
-	{ label: '娓╁害', name: 'temp' },
-	{ label: '3D', name: '3d' },
+	{ label: '娓╁害', name: 'tmp' },
+	{ label: '3D', name: '3D' },
 	{ label: '鑷剤鑳藉姏', name: 'self' },
 	{ label: '绠$悊淇℃伅', name: 'manage' },
+]);
+
+const btns = ref([
 	{ label: '鐢垫簮鍛婅鍙傛暟璁剧疆', name: 'powerAlarmSet' },
 	{ label: '鐢垫睜鍛婅鍙傛暟璁剧疆', name: 'battAlarmSet' },
 	{ label: '鍥剧墖', name: 'img' },
@@ -39,56 +81,139 @@
 const acTab = ref(tabs.value[0].name);
 
 const fullName = ref('婀栧寳鐪�-姝︽眽甯�-姝︽槍鍖�-姝︽槍鏈烘埧-鐢垫睜缁�1');
+const topData = ref({});
 
+
+function sendMessage() {
+  let params = {
+    stationId: curStationId.value || 0,
+    powerId: curPowerId.value || 0,
+    devId: curDevId.value || 0,
+    battgroupId: curBattgroupId.value || 0,
+    // system, power, vol, res, tmp, 3D, self, manage
+    pageType: acTab.value
+  };
+  sendData(JSON.stringify(params));
+}
 
 function tabClick(item) {
 	acTab.value = item.name;
+  sendMessage();
 }
+
+function btnClick(item) {
+  console.log('item', item, '=============');
+  
+}
+function leafClick(item) {
+  // console.log('item', item, '=============');
+  curStationId.value = item.stationId;
+  curPowerId.value = item.powerId;
+  curBattgroupId.value = item.battgroupId || 0;
+  curDevId.value = item.devId || 0;
+
+  sendMessage();
+
+  // if (getQueryString("stationId")) {
+  //   router.push({
+  //     path: route.path,
+  //     query: {
+  //       stationId: item.stationId,
+  //       powerId: item.powerId,
+  //       battgroupId: item.battgroupId || undefined,
+  //       devId: item.devId || undefined
+  //     }
+  //   })
+  // }
+  // fullName.value = item.fullName;
+}
+
+
+
+watch(
+  () => message.value,
+  (n) => {
+    if (n) {
+      let {data2: { topRes, realRes}} = JSON.parse(n);
+      let data = {};
+      if (topRes.code && topRes.data) {
+        data = topRes.data2;
+        fullName.value = `${data.fullName} ${data.powerName} ${data.devName} ${data.battGroupName}`;
+      }
+      topData.value = data;
+      if (realRes.code && realRes.data) {
+        // rtData.
+        rtData['system'] = realRes.data2;
+      }
+    }
+  }
+)
+
+
+
+onMounted(() => {
+  sendMessage();
+});
 </script>
 
 <template>
   <div class="page-contain">
-    <div class="page-header">
-      <div class="p-title">{{ fullName }}</div>
-      <div class="status-bar">
-        <div
-          :class="['status-item',]"
-          v-for="(item, index) in statusList"
-          :key="'status_' + index"
-        >
-          <div class="item-value">{{ item.value }}</div>
-          <div class="item-name">{{ item.label }}</div>
+    <site-list @leaf-click="leafClick"></site-list>
+    <div class="page-inner">
+      <div class="page-header">
+        <div class="p-title">{{ fullName }}</div>
+        <div class="status-bar">
+          <div
+            :class="['status-item',]"
+            v-for="(item, index) in statusList"
+            :key="'status_' + index"
+          >
+            <div class="item-value" v-if="item.prop == 'captestTimelong' || item.prop == 'restTime'">{{ topData[item.prop] ? formatSeconds(topData[item.prop]) : '--' }}</div>
+            <div class="item-value time" v-else-if="item.prop == 'recordtime'">{{ topData[item.prop] || '--' }}</div>
+            <div class="item-value" v-else>{{ topData[item.prop] || '--' }}{{ item.unit }}</div>
+            <div class="item-name">{{ item.label }}</div>
+          </div>
+        </div>
+        <div class="p-tabs">
+          <div
+            :class="['tab-item', {'active': item.name == acTab}]"
+            v-for="(item, index) in tabs"
+            :key="'tab_' + index"
+            @click="tabClick(item)"
+          >
+            {{ item.label }}
+          </div>
+          <div
+            class="btn-item tab-item"
+            v-for="(item, index) in btns"
+            :key="'btn_' + index"
+            @click="btnClick(item)"
+          >
+            {{ item.label }}
+          </div>
         </div>
       </div>
-      <div class="p-tabs">
-        <div
-          :class="['tab-item', {'active': item.name == acTab}]"
-          v-for="(item, index) in tabs"
-          :key="'tab_' + index"
-          @click="tabClick(item)"
-        >
-          {{ item.label }}
+      <div class="page-main">
+        <div class="tab-contain" v-if="acTab == 'system'">
+          <tab-system :data="rtData['system']"></tab-system>
         </div>
+        <div class="tab-contain" v-if="acTab == 'power'">
+          <tab-power></tab-power>
+        </div>
+        <div class="tab-contain" v-if="acTab == 'vol'">
+          <tab-vol></tab-vol>
+        </div>
+        <div class="tab-contain" v-if="acTab == 'res'">3</div>
+        <div class="tab-contain" v-if="acTab == 'tmp'">4</div>
+        <div class="tab-contain" v-if="acTab == '3D'">5</div>
+        <div class="tab-contain" v-if="acTab == 'self'">6</div>
+        <div class="tab-contain" v-if="acTab == 'manage'">7</div>
+        <!-- <div class="tab-contain" v-if="acTab == 'powerAlarmSet'">8</div>
+        <div class="tab-contain" v-if="acTab == 'battAlarmSet'">9</div>
+        <div class="tab-contain" v-if="acTab == 'img'">10</div>
+        <div class="tab-contain" v-if="acTab == 'history'">11</div>
+        <div class="tab-contain" v-if="acTab == 'hisRt'">12</div> -->
       </div>
-    </div>
-    <div class="page-main">
-      <div class="tab-contain" v-if="acTab == 'system'">
-        <tab-system></tab-system>
-      </div>
-      <div class="tab-contain" v-if="acTab == 'power'">
-        <tab-power></tab-power>
-      </div>
-      <div class="tab-contain" v-if="acTab == 'vol'">2</div>
-      <div class="tab-contain" v-if="acTab == 'res'">3</div>
-      <div class="tab-contain" v-if="acTab == 'temp'">4</div>
-      <div class="tab-contain" v-if="acTab == '3d'">5</div>
-      <div class="tab-contain" v-if="acTab == 'self'">6</div>
-      <div class="tab-contain" v-if="acTab == 'manage'">7</div>
-      <div class="tab-contain" v-if="acTab == 'powerAlarmSet'">8</div>
-      <div class="tab-contain" v-if="acTab == 'battAlarmSet'">9</div>
-      <div class="tab-contain" v-if="acTab == 'img'">10</div>
-      <div class="tab-contain" v-if="acTab == 'history'">11</div>
-      <div class="tab-contain" v-if="acTab == 'hisRt'">12</div>
     </div>
   </div>
 </template>
@@ -96,7 +221,15 @@
 <style scoped lang="less">
 .page-contain {
   display: flex;
-  flex-direction: column;
+  padding: 8px 8px 8px 0;
+  margin-left: 8px;
+  overflow: hidden;
+  .page-inner {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+    margin-left: 8px;
+  }
 
   .page-header {
     // border: 1px solid #0ff;
@@ -125,7 +258,7 @@
         color: #50c7f1;
         // border: 1px solid #0F6B79;
         border-radius: 6px;
-        margin: 4px 6px;
+        margin: 4px 2px;
         display: flex;
         flex-direction: column;
         justify-content: center;
@@ -140,6 +273,10 @@
           margin-bottom: 6px;
           color: #ff0;
           font-size: 20px;
+          &.time {
+            white-space: nowrap;
+            font-size: 12px;
+          }
         }
 
         .item-name {
@@ -171,6 +308,10 @@
           background: #47CAFE;
           color: #03216e;
         }
+        &.btn-item {
+          background: #076fe8;
+          // border-radius: 6px;
+        }
       }
     }
   }
diff --git a/src/views/realtime/tabs/system.vue b/src/views/realtime/tabs/system.vue
index 574443b..0056ad8 100644
--- a/src/views/realtime/tabs/system.vue
+++ b/src/views/realtime/tabs/system.vue
@@ -1,5 +1,20 @@
 <script setup>
 import { ref } from "vue";
+import svgDiagram from '@/components/svgDiagram.vue';
+import info from '@/components/info.vue';
+// import lineChart from '@/components/echarts/line1.vue';
+import lineChart from '@/components/echarts/BaseChart.vue';
+// import lineChart from '../bar8.vue';
+
+const props = defineProps({
+  data: {
+    type: Object,
+    default: {},
+  },
+});
+
+const tabIdx0 = ref(0);
+const tabIdx1 = ref(0);
 
 
 </script>
@@ -23,40 +38,42 @@
             </div>
           </div>
           <div class="p-item">
-            <div class="panel-title">褰撳墠鏍稿淇℃伅</div>
+            <div class="panel-title">涓婃鏍囧噯鏍稿淇℃伅</div>
             <div class="panel">
               <div class="panel-row">
-                <div class="label">鍋滄鍘熷洜</div>
+                <div class="label">寮�濮嬫椂闂�</div>
                 <div class="value">鏃�</div>
               </div>
               <div class="panel-row">
-                <div class="label">鍋滄鍘熷洜</div>
+                <div class="label">娴嬭瘯瀹归噺</div>
                 <div class="value">鏃�</div>
               </div>
               <div class="panel-row">
-                <div class="label">鍋滄鍘熷洜</div>
+                <div class="label">娴嬭瘯鏃堕暱</div>
                 <div class="value">鏃�</div>
               </div>
               <div class="panel-row">
-                <div class="label">鍋滄鍘熷洜</div>
+                <div class="label">棰勪及瀹归噺</div>
                 <div class="value">鏃�</div>
               </div>
               <div class="panel-row">
-                <div class="label">鍋滄鍘熷洜</div>
+                <div class="label">缁堟鍘熷洜</div>
                 <div class="value">鏃�</div>
               </div>
             </div>
           </div>
         </div>
-        <div class="p-main"></div>
+        <div class="p-main">
+          <svg-diagram class="svg-diagram"></svg-diagram>
+        </div>
         <div class="p-right">
           <div class="control-contain">
-            <div class="control-title">鎺у埗绠$悊</div>
+            <div class="control-title"><svg-icon icon-class="controls"></svg-icon><span>鎺у埗绠$悊</span></div>
             <div class="control-btn">鏍稿娴嬭瘯</div>
             <div class="control-btn">鍋滄鏍稿娴嬭瘯</div>
           </div>
           <div class="control-contain">
-            <div class="control-title">璁惧绠$悊</div>
+            <div class="control-title"><svg-icon icon-class="dev"></svg-icon><span>璁惧绠$悊</span></div>
             <div class="control-btn">杩滅▼閲嶅惎</div>
             <div class="control-btn">绯荤粺鍙傛暟璁剧疆</div>
             <div class="control-btn">鍛婅鍙傛暟璁剧疆</div>
@@ -69,16 +86,72 @@
     </div>
     <div class="row row2">
       <div class="card-item">
-        <card title="浜ゆ祦杈撳叆"></card>
+        <card title="浜ゆ祦杈撳叆">
+          <template #tools>
+            <el-radio-group class="tab-idx" v-model="tabIdx0" size="small">
+              <el-radio-button label="鐢垫祦" :value="0" />
+              <el-radio-button label="鐢靛帇" :value="1" />
+            </el-radio-group>
+            <svg-icon class-name="btn-setting" icon-class="setting"></svg-icon>
+          </template>
+          <line-chart></line-chart>
+        </card>
       </div>
       <div class="card-item">
-        <card title="鐩存祦杈撳叆"></card>
+        <card title="鐩存祦杈撳叆">
+          <template #tools>
+            <el-radio-group class="tab-idx" v-model="tabIdx1" size="small">
+              <el-radio-button label="鐢垫祦" :value="0" />
+              <el-radio-button label="鐢靛帇" :value="1" />
+            </el-radio-group>
+            <svg-icon class-name="btn-setting" icon-class="setting"></svg-icon>
+          </template>
+        </card>
       </div>
       <div class="card-item">
-        <card title="鏍稿璁惧淇℃伅"></card>
+        <card title="鏍稿璁惧淇℃伅">
+          <template #tools>
+            <svg-icon class-name="btn-setting" icon-class="setting"></svg-icon>
+          </template>
+        </card>
       </div>
       <div class="card-item">
-        <card title="钃勭數姹犱俊鎭�"></card>
+        <card title="钃勭數姹犱俊鎭�">
+          <div class="batt grid">
+            <info
+              label="鏈�澶у閲�"
+              value="#3 8Ah"
+            ></info>
+            <info
+              label="鏈�灏忓閲�"
+              value="100"
+            ></info>
+            <info
+              label="鏈�楂樺唴闃�"
+              value="100"
+            ></info>
+            <info
+              label="鏈�浣庡唴闃�"
+              value="100"
+            ></info>
+            <info
+              label="鏈�楂樼數鍘�"
+              value="100"
+            ></info>
+            <info
+              label="鏈�浣庣數鍘�"
+              value="100"
+            ></info>
+            <info
+              label="鏈�楂樻俯搴�"
+              value="100"
+            ></info>
+            <info
+              label="鏈�浣庢俯搴�"
+              value="100"
+            ></info>
+          </div>
+        </card>
       </div>
     </div>
   </div>
@@ -106,6 +179,9 @@
         margin: 8px;
         display: flex;
         flex-direction: column;
+        .panel-title {
+          font-size: 24px;
+        }
         .panel {
           border: 1px solid #5FA9CF;
           background: #073451;
@@ -162,6 +238,9 @@
           text-align: center;
           font-weight: 700;
           margin-bottom: 4px;
+          span {
+            margin-left: 0.6em;
+          }
         }
         .control-btn {
           margin: 2px 4px;
@@ -206,5 +285,58 @@
       }
     }
   }
+  .svg-diagram {
+    
+  }
+
+  .btn-setting {
+    display: inline-block;
+    cursor: pointer;
+    margin-left: 0.4em;
+    transition: all 1.3s ease;
+    &:hover {
+      transform: rotate(360deg);
+      color: #FDFE01;
+    }
+  }
+
+  .batt.grid {
+    height: 100%;
+    padding: 4px 6px;
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    // grid-template-rows: repeat(4, 1fr);
+    gap: 12px;
+    place-content: center;
+  }
+
+  .tab-idx {
+
+    :deep(.el-radio-button:first-child .el-radio-button__inner) {
+      border-left: 1px solid #4D81BA;
+      border-radius: 0;
+      box-shadow: none !important;
+    }
+    // .el-radio-button--small .el-radio-button__inner {
+    //     border-radius: 0;
+    //     font-size: 12px;
+    //     padding: 5px 11px;
+    // }
+    :deep(.el-radio-button__inner) {
+        background: #183A55;
+        border: 1px solid #4D81BA;
+        border-left: 0;
+        border-radius: 0;
+        color: #fff;
+        font-weight: 500;
+        padding: 4px 10px;
+    }
+    :deep(.el-radio-button.is-active .el-radio-button__original-radio:not(:disabled)+.el-radio-button__inner) {
+        background: linear-gradient(69deg, #6EABE4, #6EABE4 25%,  #0A3E77 75%, #0A3E77);
+        // border: 0 none;
+        box-shadow: none;
+        color: #fff;
+    }
+  }
 }
 </style>
\ No newline at end of file
diff --git a/src/views/realtime/tabs/vol.vue b/src/views/realtime/tabs/vol.vue
new file mode 100644
index 0000000..9044e8c
--- /dev/null
+++ b/src/views/realtime/tabs/vol.vue
@@ -0,0 +1,16 @@
+<script setup>
+import { ref } from "vue";
+import test from '@/components/info.vue';
+
+</script>
+
+<template>
+  <test
+    label="鐢靛帇"
+    value="123"
+  ></test>
+</template>
+
+<style scoped lang="less">
+
+</style>
\ No newline at end of file
diff --git a/src/views/statistics/batt.vue b/src/views/statistics/batt.vue
new file mode 100644
index 0000000..166cff9
--- /dev/null
+++ b/src/views/statistics/batt.vue
@@ -0,0 +1,516 @@
+<script setup name="batt">
+	import { ref, reactive, onMounted, computed } from "vue";
+	import { storeToRefs } from "pinia";
+	import { Search, Plus } from "@element-plus/icons-vue";
+	import ycCard from "@/components/ycCard/index.vue";
+	import addEdit from "../datas/addEdit.vue";
+	import { ElMessage } from "element-plus";
+	import useElement from "@/hooks/useElement.js";
+  import { useUserStore } from '@/store/user';
+
+  import useStation from "@/hooks/useStationList.js";
+  const { provice, city, country, stationName,
+    proviceList, cityList, countryList, stationList,
+  } = useStation();
+
+  import powerTypes from '@/utils/const/const_powerType.js';
+  import {
+    delBatt,
+  } from "@/api/station";
+
+  const userStore = useUserStore();
+  const { uid, uname } = storeToRefs(userStore);
+
+
+
+  import {
+    getDevList,
+  } from "@/api/station";
+
+	const { $loading, $message, $confirm } = useElement();
+
+	const headers = [
+    {
+			prop: "provice",
+			label: "鐪�",
+			width: "80",
+		},
+    {
+			prop: "city",
+			label: "甯�",
+			width: "80",
+		},
+    {
+			prop: "country",
+			label: "鍖哄幙",
+			width: "80",
+		},
+		{
+			prop: "stationName",
+			label: "鏈烘埧鍚嶇О",
+			width: "160",
+		},
+    {
+			prop: "stationType",
+			label: "鐢靛帇绛夌骇",
+			width: "80",
+		},
+    {
+			prop: "longitude",
+			label: "缁忓害",
+			width: "80",
+		},
+    {
+			prop: "latitude",
+			label: "绾害",
+			width: "80",
+		},
+    {
+			prop: "powerName",
+			label: "鐢垫簮鍚嶇О",
+			width: "80",
+		},
+    {
+			prop: "powerTypeStr",
+			label: "鐢垫簮绫诲瀷",
+			width: "80",
+		},
+    {
+			prop: "company",
+			label: "鐢垫簮鍝佺墝",
+			width: "80",
+		},
+		{
+			prop: "powerModel",
+			label: "鐢垫簮鍨嬪彿",
+			width: "80",
+		},
+    {
+			prop: "protocol",
+			label: "鐢垫簮鍗忚",
+			width: "80",
+		},
+    {
+			prop: "powerIp",
+			label: "鐢垫簮IP",
+			width: "120",
+		},
+    {
+			prop: "devName",
+			label: "璁惧鍚嶇О",
+			width: "120",
+		},
+    {
+			prop: "devType",
+			label: "璁惧鍨嬪彿",
+			width: "120",
+		},
+    {
+			prop: "devIp",
+			label: "璁惧IP",
+			width: "120",
+		},
+    {
+			prop: "battgroupName",
+			label: "鐢垫睜缁勫悕绉�",
+			width: "120",
+		},
+    {
+			prop: "moncount",
+			label: "鐢垫睜鍗曚綋涓暟",
+			width: "120",
+		},
+    {
+			prop: "monvolstd",
+			label: "鐢垫睜鏍囩О鐢靛帇",
+			width: "120",
+		},
+    {
+			prop: "moncapstd",
+			label: "鐢垫睜鏍囩О瀹归噺",
+			width: "120",
+		},
+    {
+			prop: "monresstd",
+			label: "鐢垫睜鏍囩О鍐呴樆",
+			width: "120",
+		},
+    {
+			prop: "product",
+			label: "鐢垫睜鍝佺墝",
+			width: "120",
+		},
+    {
+			prop: "battModel",
+			label: "鐢垫睜鍨嬪彿",
+			width: "120",
+		},
+	];
+	const background = ref(true);
+	const disabled = ref(false);
+	const pageCurr = ref(1);
+	const pageSize = ref(10);
+	const total = ref(0);
+	const addEditVisible = ref(false);
+	const dialogTitle = ref("");
+	const currentAreaId = ref();
+	const currentAreaIds = ref([]);
+	const datas = reactive({
+		tableData: [],
+		rowData: {},
+	});
+
+	// const tableData = reactive([]);
+	// const rowData = reactive({});
+
+	// const userStore = useUserStore();
+	// const { uid, uname } = storeToRefs(userStore);
+
+	function getList() {
+		let loading = $loading();
+    let params = {
+      // city: "",
+      // country: "",
+      pageNum: pageCurr.value,
+      pageSize: pageSize.value,
+      // powerName: "",
+      // provice: "",
+      // stationName: "",
+    };
+
+		getDevList(params)
+			.then((res) => {
+				let { code, data, data2 } = res;
+				let list = [];
+				let _total = 0;
+				if (code && data) {
+					// console.log(data);
+					list = data2.list.map(v => ({
+            ...v,
+            powerTypeStr: powerTypes[v.powerType],
+            // roleName: roles[v.role],
+          }));
+					_total = data2.total;
+				}
+				loading.close();
+				// tableData.length = 0;
+				// tableData.push(...list);
+				datas.tableData = list;
+				total.value = _total;
+			})
+			.catch((err) => {
+				console.log(err);
+				loading.close();
+			});
+	}
+
+	// 灞曠ず鏁版嵁鏁伴噺
+	function handleSizeChange(val) {
+		pageSize.value = val;
+		getList();
+	}
+	// 缈婚〉
+	function handleCurrentChange(val) {
+		pageCurr.value = val;
+		getList();
+	}
+	function add() {
+		dialogTitle.value = "娣诲姞璁惧";
+		datas.rowData = {};
+		addEditVisible.value = true;
+	}
+	function edit(record) {
+		dialogTitle.value = "缂栬緫璁惧";
+		addEditVisible.value = true;
+		console.log(record, '=======edit======');
+		// if (record.ainfList.idPath) {
+		// 	ids = record.ainfList.idPath.split("_").map((v) => v * 1);
+		// }
+		// ids.push(record.areaId);
+
+		datas.rowData = { ...record };
+	}
+
+  function addBatt(record) {
+    dialogTitle.value = "娣诲姞鐢垫睜缁�";
+		datas.rowData = { ...record, addBattFlag: true };
+		addEditVisible.value = true;
+  }
+  
+	function confirmRemove(record) {
+		$confirm("鍒犻櫎", () => {
+      let { stationId, powerId, battgroupId } = record;
+			remove(stationId, powerId, battgroupId||undefined);
+		});
+	}
+	function remove(stationId, powerId, battgroupId) {
+		let loading = $loading();
+		delBatt(stationId, powerId, battgroupId)
+			.then((res) => {
+				let { code, data } = res;
+				loading.close();
+				if (code && data) {
+					$message.success("鎿嶄綔鎴愬姛");
+					handleCurrentChange(1);
+				} else {
+					$message.success("鎿嶄綔澶辫触");
+				}
+			})
+			.catch((err) => {
+				loading.close();
+				console.log(err);
+			});
+	}
+	function onOk() {
+		addEditVisible.value = false;
+		handleCurrentChange(1);
+	}
+	function onCanel() {
+		addEditVisible.value = false;
+	}
+
+
+	onMounted(() => {
+		getList();
+	});
+</script>
+
+<template>
+  <div class="page-wrapper">
+    <!-- <div class="page-header">
+    </div> -->
+    <div class="page-content">
+      <yc-card is-full>
+        <div class="page-content-wrapper">
+          <div class="page-content-tools page-filter">
+            <div class="table-row">
+              <div class="table-cell text-right">鐪侊細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="provice"
+                  size="small"
+                  placeholder="璇烽�夋嫨鐪�"
+                >
+                  <el-option
+                    v-for="item in proviceList"
+                    :key="'l0_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">甯傦細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="city"
+                  size="small"
+                  placeholder="璇烽�夋嫨甯�"
+                >
+                  <el-option
+                    v-for="item in cityList"
+                    :key="'l1_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鍖哄幙:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="country"
+                  size="small"
+                  placeholder="璇烽�夋嫨鍖哄幙"
+                >
+                  <el-option
+                    v-for="item in countryList"
+                    :key="'l2_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">绔欑偣:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="stationName"
+                  size="small"
+                  placeholder="璇烽�夋嫨绔欑偣"
+                >
+                  <el-option
+                    v-for="item in stationList"
+                    :key="'l3_' + item.stationId"
+                    :label="item.stationName"
+                    :value="item.stationName"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鍝佺墝:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="country"
+                  size="small"
+                  placeholder="璇烽�夋嫨鍝佺墝"
+                >
+                  <el-option
+                    v-for="item in countryList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鐢靛帇绛夌骇:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="country"
+                  size="small"
+                  placeholder="璇烽�夋嫨鐢靛帇绛夌骇"
+                >
+                  <el-option
+                    v-for="item in countryList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-table">
+            <div class="pos-rel">
+              <div class="pos-abs">
+                <el-table class="yc-table" stripe height="100%" size="small" :data="datas.tableData" style="width: 100%">
+                  <el-table-column type="index" fixed="left" label="搴忓彿" width="60"></el-table-column>
+                  <el-table-column v-for="header in headers" :key="header.prop" :prop="header.prop" :label="header.label"
+                    :min-width="header.width" align="center">
+                    <template #default="scope">{{ scope.row[header.prop] || '--' }}</template>  
+                  </el-table-column>
+                  <el-table-column label="鎿嶄綔" fixed="right" width="240" align="center">
+                    <template #default="scope">
+                      <el-button type="primary" size="small"
+                        @click="edit(scope.row)">缂栬緫</el-button>
+                      <el-button type="danger" size="small" 
+                        @click="confirmRemove(scope.row)">鍒犻櫎</el-button>
+                      <el-button type="primary" size="small" 
+                        @click="addBatt(scope.row)">娣诲姞鐢垫睜缁�</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-page">
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="add" :icon="Plus">娣诲姞</el-button>
+              <el-button type="primary" round size="small" @click="getList" :icon="Search">鏌ヨ</el-button>
+            </div>
+            <div class="el-page-container">
+              <el-pagination v-model:current-page="pageCurr" v-model:page-size="pageSize"
+                :page-sizes="[20, 40, 60, 80, 100, 200, 300, 400]" size="small" :disabled="disabled"
+                :background="background" layout="total, sizes, prev, pager, next, jumper" :total="total"
+                @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+            </div>
+            <div class="page-tool"></div>
+          </div>
+        </div>
+      </yc-card>
+    </div>
+    <div class="page-footer"></div>
+    <!-- 寮圭獥 -->
+    <el-dialog :title="dialogTitle" v-model="addEditVisible" top="0" :close-on-click-modal="false" class="dialog-center"
+      width="860px" center>
+      <add-edit v-if="addEditVisible" @success="onOk" :info="datas.rowData" @cancel="onCanel"></add-edit>
+    </el-dialog>
+  </div>
+</template>
+
+<style scoped lang="less">
+.page-wrapper {
+  display: flex;
+  flex-direction: row;
+  // padding: 8px;
+  height: 100%;
+
+  .page-content {
+    flex: 1;
+  }
+}
+
+.page-content-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .page-content-tools {
+    padding-bottom: 8px;
+  }
+
+  .page-content-table {
+    // border-top: 1px solid var(--border-light-color);
+    box-sizing: border-box;
+    flex: 1;
+    margin-left: 26px;
+    margin-right: 26px;
+  }
+
+  .page-content-page {
+    padding: 8px 8px 0 8px;
+    text-align: center;
+
+    .el-page-container {
+      display: inline-block;
+      padding: 0 16px;
+    }
+
+    .page-tool {
+      display: inline-block;
+    }
+  }
+}
+
+.hdw-card-container {
+  width: 240px;
+  padding-right: 8px;
+  height: 100%;
+}
+
+.pos-rel {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .pos-abs {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.tools-filter {
+  display: inline-block;
+  font-size: 14px;
+
+  .tools-filter-item {
+    display: inline-block;
+    margin-right: 8px;
+
+    .filter-label {
+      display: inline-block;
+    }
+
+    .filter-content {
+      display: inline-block;
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/statistics/battHr.vue b/src/views/statistics/battHr.vue
new file mode 100644
index 0000000..5065b45
--- /dev/null
+++ b/src/views/statistics/battHr.vue
@@ -0,0 +1,487 @@
+<script setup name="battHr">
+	import { ref, reactive, onMounted, computed, nextTick } from "vue";
+	import { storeToRefs } from "pinia";
+	import { Search, Plus } from "@element-plus/icons-vue";
+	import ycCard from "@/components/ycCard/index.vue";
+	import addEdit from "../datas/addEdit.vue";
+	import { ElMessage } from "element-plus";
+	import useElement from "@/hooks/useElement.js";
+  import { useUserStore } from '@/store/user';
+
+  import useStation from "@/hooks/useStationList.js";
+  const { provice, city, country, stationName,
+    proviceList, cityList, countryList, stationList,
+  } = useStation();
+
+  import { useRouter } from "vue-router";
+  const router = useRouter();
+
+  import { ExportFile } from '@/utils/exportFile.js';
+
+
+  import powerTypes from '@/utils/const/const_powerType.js';
+  import hrTypes from '@/utils/const/const_hrTestType.js';
+  import {
+    delBatt,
+  } from "@/api/station";
+
+  const userStore = useUserStore();
+  const { uid, uname } = storeToRefs(userStore);
+  import moment from 'moment';
+
+  import formatSeconds from '@/utils/formatSeconds';
+  import {
+    toFixed,
+    digits,
+  } from '@/utils/toFixed';
+
+  import {
+    getBattTinfStatistic,
+  } from "@/api/statistic.js";
+
+	const { $loading, $message, $confirm } = useElement();
+
+
+	const headers = [
+		{
+			prop: "fullName",
+			label: "绔欑偣鍚嶇О",
+			width: "160",
+		},
+    {
+			prop: "battgroupName",
+			label: "鐢垫睜缁勫悕绉�",
+			width: "80",
+		},
+    {
+			prop: "testType",
+			label: "娴嬭瘯绫诲瀷",
+			width: "80",
+		},
+    {
+			prop: "testStarttime",
+			label: "娴嬭瘯寮�濮嬫椂闂�",
+			width: "80",
+		},
+    {
+      prop: "testCurr",
+      label: "娴嬭瘯鐢垫祦",
+      width: "80",
+    },
+    {
+      prop: "testCap",
+      label: "娴嬭瘯瀹归噺",
+      width: "80",
+    },
+    {
+      prop: "testTimelongStr",
+      label: "娴嬭瘯鏃堕暱",
+      width: "80",
+    },
+    {
+			prop: "testStoptype",
+			label: "鍋滄鍘熷洜",
+			width: "80",
+		},
+    {
+			prop: "restCap",
+			label: "棰勪及瀹為檯瀹归噺",
+			width: "80",
+		},
+    {
+			prop: "precentCapStr",
+			label: "瀹归噺鐧惧垎姣�",
+			width: "80",
+		},
+    {
+			prop: "restTimeStr",
+			label: "棰勪及缁埅鏃堕棿",
+			width: "80",
+		},
+	];
+
+   const testType = ref('');
+  const hrTypeList = computed(() => {
+    return Object.keys(hrTypes).map(v => {
+      return {
+        value: v,
+        label: hrTypes[v],
+      };
+    });
+  });
+
+  
+	const background = ref(true);
+	const disabled = ref(false);
+	const pageCurr = ref(1);
+	const pageSize = ref(10);
+	const total = ref(0);
+	const addEditVisible = ref(false);
+	const dialogTitle = ref("");
+	const currentAreaId = ref();
+	const currentAreaIds = ref([]);
+  const testStartDate = ref('');
+  const testEndDate = ref('');
+	const datas = reactive({
+		tableData: [],
+		rowData: {},
+	});
+
+
+  // 璁$畻灞炴�х敓鎴� picker-options锛堟洿绠�娲侊級
+  const startDisabledDate = (time) =>  testEndDate.value ? moment(testEndDate.value).isBefore(time) || moment().isBefore(time) : moment().isBefore(time);
+
+  const endDisabledDate = (time) => testStartDate.value ? moment(time).isBefore(testStartDate.value) || moment().isBefore(time) : moment().isBefore(time);
+
+	function getList() {
+		let loading = $loading();
+    let params = {
+      provice: provice.value || undefined,
+      city: city.value || undefined,
+      country: country.value || undefined,
+      stationName: stationName.value || undefined,
+      testType: testType.value || undefined,
+      testStartTime: testStartDate.value ? testStartDate.value + ' 00:00:00' : undefined,
+      testEndTime: testEndDate.value ? testEndDate.value + ' 23:59:59' : undefined,
+      pageNum: pageCurr.value,
+      pageSize: pageSize.value,
+    };
+
+		getBattTinfStatistic(params)
+			.then((res) => {
+				let { code, data, data2 } = res;
+				let list = [];
+				let _total = 0;
+				if (code && data) {
+					// console.log(data);
+					list = data2.list.map(v => ({
+            ...v,
+            testTimelongStr: formatSeconds(v.testTimelong),
+            restTimeStr: formatSeconds(v.restTime),
+            precentCapStr: toFixed(v.precentCap, digits.PREC) + '%',
+          }));
+					_total = data2.total;
+				}
+				loading.close();
+				// tableData.length = 0;
+				// tableData.push(...list);
+				datas.tableData = list;
+				total.value = _total;
+			})
+			.catch((err) => {
+				console.log(err);
+				loading.close();
+			});
+	}
+
+	// 灞曠ず鏁版嵁鏁伴噺
+	function handleSizeChange(val) {
+		pageSize.value = val;
+		getList();
+	}
+	// 缈婚〉
+	function handleCurrentChange(val) {
+		pageCurr.value = val;
+		getList();
+	}
+
+  function filterChange() {
+    nextTick(() => {
+      pageCurr.value = 1;
+      getList();
+    });
+  }
+
+  function goRt (row) {
+    router.push({
+      path: '/datas/realtime',
+      query: {
+        stationId: row.stationId || undefined,
+        powerId: row.powerId || undefined,
+        devId: row.devId || undefined,
+        battgroupId: row.battgroupId || undefined,
+        pageFlag: Math.random(),
+      }
+    });
+  }
+
+  function goHis (row) {
+    router.push({
+      path: '/datas/realtime',
+      query: {
+        id: row.battgroupId
+      }
+    });
+  }
+
+  function exportExcel() {
+    ExportFile(headers, datas.tableData, "钃勭數姹犳牳瀹逛俊鎭粺璁�");
+  }
+
+	onMounted(() => {
+		getList();
+	});
+</script>
+
+<template>
+  <div class="page-wrapper">
+    <!-- <div class="page-header">
+    </div> -->
+    <div class="page-content">
+      <yc-card is-full>
+        <div class="page-content-wrapper">
+          <div class="page-content-tools page-filter">
+            <div class="grid-container" :style="{'--counter': 8}">
+              <div class="grid-item">
+                <div class="label">鐪�</div>
+                <div class="value">
+                  <el-select
+                    v-model="provice"
+                    size="small"
+                    clearable
+                    @change="filterChange"
+                    placeholder="璇烽�夋嫨鐪�"
+                  >
+                    <el-option
+                      v-for="item in proviceList"
+                      :key="'l0_' + item"
+                      :label="item"
+                      :value="item"
+                    >
+                    </el-option>
+                  </el-select>
+                </div>
+              </div>
+              <div class="grid-item">
+                <div class="label">甯�</div>
+                <div class="value">
+                  <el-select
+                    v-model="city"
+                    size="small"
+                    clearable
+                    @change="filterChange"
+                    placeholder="璇烽�夋嫨甯�"
+                  >
+                    <el-option
+                      v-for="item in cityList"
+                      :key="'l1_' + item"
+                      :label="item"
+                      :value="item"
+                    >
+                    </el-option>
+                  </el-select>
+                </div>
+              </div>
+              <div class="grid-item">
+                <div class="label">鍖哄幙</div>
+                <div class="value">
+                  <el-select
+                    v-model="country"
+                    clearable
+                    @change="filterChange"
+                    size="small"
+                    placeholder="璇烽�夋嫨鍖哄幙"
+                  >
+                    <el-option
+                      v-for="item in countryList"
+                      :key="'l2_' + item"
+                      :label="item"
+                      :value="item"
+                    >
+                    </el-option>
+                  </el-select>
+                </div>
+              </div>
+              <div class="grid-item">
+                <div class="label">绔欑偣</div>
+                <div class="value">
+                  <el-select
+                    v-model="stationName"
+                    clearable
+                    @change="filterChange"
+                    size="small"
+                    placeholder="璇烽�夋嫨绔欑偣"
+                  >
+                    <el-option
+                      v-for="item in stationList"
+                      :key="'l3_' + item.stationId"
+                      :label="item.stationName"
+                      :value="item.stationName"
+                    >
+                    </el-option>
+                  </el-select>
+                </div>
+              </div>
+              <div class="grid-item">
+                <div class="label">娴嬭瘯绫诲瀷</div>
+                <div class="value">
+                  <el-select
+                    v-model="testType"
+                    size="small"
+                    clearable
+                    @change="filterChange"
+                    placeholder="璇烽�夋嫨"
+                  >
+                    <el-option
+                      v-for="(item, idx) in hrTypeList"
+                      :key="'list5_' + idx"
+                      :label="item.label"
+                      :value="item.value"
+                    >
+                    </el-option>
+                  </el-select>
+                </div>
+              </div>
+              <div class="grid-item">
+                <div class="label">娴嬭瘯寮�濮嬫椂闂�</div>
+                <div class="value" style="grid-column: span 5;">
+                  <el-date-picker
+                    v-model="testStartDate"
+                    type="date"
+                    size="small"
+                    placeholder="閫夋嫨鏃ユ湡"
+                    format="YYYY-MM-DD"
+                    value-format="YYYY-MM-DD"
+                    :disabled-date="startDisabledDate"
+                  />
+                  -
+                  <el-date-picker
+                    v-model="testEndDate"
+                    size="small"
+                    type="date"
+                    placeholder="閫夋嫨鏃ユ湡"
+                    format="YYYY-MM-DD"
+                    value-format="YYYY-MM-DD"
+                    :disabled-date="endDisabledDate"
+                  />
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-table">
+            <div class="pos-rel">
+              <div class="pos-abs">
+                <el-table class="yc-table" stripe height="100%" size="small" :data="datas.tableData" style="width: 100%">
+                  <el-table-column type="index" fixed="left" label="搴忓彿" width="60"></el-table-column>
+                  <el-table-column v-for="header in headers" :key="header.prop" :prop="header.prop" :label="header.label"
+                    :min-width="header.width" align="center">
+                    <template #default="scope">{{ scope.row[header.prop] }}</template>  
+                  </el-table-column>
+                  <el-table-column label="鎿嶄綔" fixed="right" width="240" align="center">
+                    <template #default="scope">
+                      <el-button type="warning" size="small" @click="goRt(scope.row)">瀹炴椂鐩戞祴</el-button>
+                      <el-button type="primary" size="small" @click="goHis(scope.row)">鍘嗗彶鏁版嵁</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-page">
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="getList" :icon="Search">鏌ヨ</el-button>
+            </div>
+            <div class="el-page-container">
+              <el-pagination v-model:current-page="pageCurr" v-model:page-size="pageSize"
+                :page-sizes="[20, 40, 60, 80, 100, 200, 300, 400]" size="small" :disabled="disabled"
+                :background="background" layout="total, sizes, prev, pager, next, jumper" :total="total"
+                @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+            </div>
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="exportExcel">瀵煎嚭</el-button>
+            </div>
+          </div>
+        </div>
+      </yc-card>
+    </div>
+    <div class="page-footer"></div>
+  </div>
+</template>
+
+<style scoped lang="less">
+.page-wrapper {
+  display: flex;
+  flex-direction: row;
+  // padding: 8px;
+  height: 100%;
+
+  .page-content {
+    flex: 1;
+  }
+}
+
+.page-content-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .page-content-tools {
+    padding-bottom: 8px;
+  }
+
+  .page-content-table {
+    // border-top: 1px solid var(--border-light-color);
+    box-sizing: border-box;
+    flex: 1;
+    margin-left: 26px;
+    margin-right: 26px;
+  }
+
+  .page-content-page {
+    padding: 8px 8px 0 8px;
+    text-align: center;
+
+    .el-page-container {
+      display: inline-block;
+      padding: 0 16px;
+    }
+
+    .page-tool {
+      display: inline-block;
+    }
+  }
+}
+
+.hdw-card-container {
+  width: 240px;
+  padding-right: 8px;
+  height: 100%;
+}
+
+.pos-rel {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .pos-abs {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.tools-filter {
+  display: inline-block;
+  font-size: 14px;
+
+  .tools-filter-item {
+    display: inline-block;
+    margin-right: 8px;
+
+    .filter-label {
+      display: inline-block;
+    }
+
+    .filter-content {
+      display: inline-block;
+    }
+  }
+}
+.max-width {
+  max-width: 200px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/statistics/devWorkstatus.vue b/src/views/statistics/devWorkstatus.vue
new file mode 100644
index 0000000..ad76cfe
--- /dev/null
+++ b/src/views/statistics/devWorkstatus.vue
@@ -0,0 +1,416 @@
+<script setup name="devWorkstatus">
+	import { ref, reactive, onMounted, computed, nextTick } from "vue";
+	import { storeToRefs } from "pinia";
+	import { Search, Plus } from "@element-plus/icons-vue";
+	import ycCard from "@/components/ycCard/index.vue";
+	import addEdit from "../datas/addEdit.vue";
+	import { ElMessage } from "element-plus";
+	import useElement from "@/hooks/useElement.js";
+  import { useUserStore } from '@/store/user';
+
+  import useStation from "@/hooks/useStationList.js";
+  const { provice, city, country, stationName,
+    proviceList, cityList, countryList, stationList,
+  } = useStation();
+
+  import { useRouter } from "vue-router";
+  const router = useRouter();
+
+  import { ExportFile } from '@/utils/exportFile.js';
+
+
+  import powerTypes from '@/utils/const/const_powerType.js';
+  import {
+    delBatt,
+  } from "@/api/station";
+
+  const userStore = useUserStore();
+  const { uid, uname } = storeToRefs(userStore);
+
+
+
+  import {
+    getDeviceStateStatistic,
+  } from "@/api/statistic.js";
+
+	const { $loading, $message, $confirm } = useElement();
+
+	const headers = [
+		{
+			prop: "fullName",
+			label: "绔欑偣鍚嶇О",
+			width: "160",
+		},
+    {
+			prop: "devWorkstate",
+			label: "璁惧鐘舵��",
+			width: "80",
+		},
+    {
+			prop: "devCaptestOnlinevol",
+			label: "鍦ㄧ嚎鐢靛帇",
+			width: "80",
+		},
+    {
+			prop: "devCaptestGroupvol",
+			label: "缁勭鐢靛帇",
+			width: "80",
+		},
+    {
+			prop: "devCaptestCurr",
+			label: "缁勭鐢垫祦",
+			width: "80",
+		},
+    {
+			prop: "devTemp",
+			label: "璁惧娓╁害",
+			width: "80",
+		},
+    {
+			prop: "devCaptestTimelong",
+			label: "娴嬭瘯鏃堕暱",
+			width: "80",
+		},
+    {
+			prop: "devCaptestCap",
+			label: "娴嬭瘯瀹归噺",
+			width: "80",
+		},
+	];
+
+  const workstateList = [{
+    value: 1,
+    label: '杩愯涓�',
+  },];
+  const devWorkstate = ref('');
+
+	const background = ref(true);
+	const disabled = ref(false);
+	const pageCurr = ref(1);
+	const pageSize = ref(10);
+	const total = ref(0);
+	const addEditVisible = ref(false);
+	const dialogTitle = ref("");
+	const currentAreaId = ref();
+	const currentAreaIds = ref([]);
+	const datas = reactive({
+		tableData: [],
+		rowData: {},
+	});
+
+	// const tableData = reactive([]);
+	// const rowData = reactive({});
+
+	// const userStore = useUserStore();
+	// const { uid, uname } = storeToRefs(userStore);
+
+
+	function getList() {
+		let loading = $loading();
+    let params = {
+      provice: provice.value || undefined,
+      city: city.value || undefined,
+      country: country.value || undefined,
+      stationName: stationName.value || undefined,
+      devWorkstate: devWorkstate.value || undefined,
+      pageNum: pageCurr.value,
+      pageSize: pageSize.value,
+    };
+
+		getDeviceStateStatistic(params)
+			.then((res) => {
+				let { code, data, data2 } = res;
+				let list = [];
+				let _total = 0;
+				if (code && data) {
+					// console.log(data);
+					list = data2.list.map(v => ({
+            ...v,
+          }));
+					_total = data2.total;
+				}
+				loading.close();
+				// tableData.length = 0;
+				// tableData.push(...list);
+				datas.tableData = list;
+				total.value = _total;
+			})
+			.catch((err) => {
+				console.log(err);
+				loading.close();
+			});
+	}
+
+	// 灞曠ず鏁版嵁鏁伴噺
+	function handleSizeChange(val) {
+		pageSize.value = val;
+		getList();
+	}
+	// 缈婚〉
+	function handleCurrentChange(val) {
+		pageCurr.value = val;
+		getList();
+	}
+
+  function filterChange() {
+    nextTick(() => {
+      pageCurr.value = 1;
+      getList();
+    });
+  }
+
+  function goRt (row) {
+    router.push({
+      path: '/datas/realtime',
+      query: {
+        stationId: row.stationId,
+        powerId: row.powerId,
+        devId: row.devId,
+        pageFlag: Math.random(),
+      }
+    });
+  }
+
+  function goHis (row) {
+    router.push({
+      path: '/datas/realtime',
+      query: {
+        id: row.battgroupId
+      }
+    });
+  }
+
+  function exportExcel() {
+    ExportFile(headers, datas.tableData, "璁惧宸ヤ綔鐘舵�佺粺璁�");
+  }
+
+	onMounted(() => {
+		getList();
+	});
+</script>
+
+<template>
+  <div class="page-wrapper">
+    <!-- <div class="page-header">
+    </div> -->
+    <div class="page-content">
+      <yc-card is-full>
+        <div class="page-content-wrapper">
+          <div class="page-content-tools page-filter">
+            <div class="table-row">
+              <div class="table-cell text-right">鐪侊細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="provice"
+                  size="small"
+                  clearable
+                  @change="filterChange"
+                  placeholder="璇烽�夋嫨鐪�"
+                >
+                  <el-option
+                    v-for="item in proviceList"
+                    :key="'l0_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">甯傦細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="city"
+                  size="small"
+                  clearable
+                  @change="filterChange"
+                  placeholder="璇烽�夋嫨甯�"
+                >
+                  <el-option
+                    v-for="item in cityList"
+                    :key="'l1_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鍖哄幙:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="country"
+                  clearable
+                  @change="filterChange"
+                  size="small"
+                  placeholder="璇烽�夋嫨鍖哄幙"
+                >
+                  <el-option
+                    v-for="item in countryList"
+                    :key="'l2_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">绔欑偣:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="stationName"
+                  clearable
+                  @change="filterChange"
+                  size="small"
+                  placeholder="璇烽�夋嫨绔欑偣"
+                >
+                  <el-option
+                    v-for="item in stationList"
+                    :key="'l3_' + item.stationId"
+                    :label="item.stationName"
+                    :value="item.stationName"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">璁惧宸ヤ綔鐘舵��:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="devWorkstate"
+                  size="small"
+                  clearable
+                  @change="filterChange"
+                  placeholder="璇烽�夋嫨"
+                >
+                  <el-option
+                    v-for="(item, idx) in workstateList"
+                    :key="'list5_' + idx"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-table">
+            <div class="pos-rel">
+              <div class="pos-abs">
+                <el-table class="yc-table" stripe height="100%" size="small" :data="datas.tableData" style="width: 100%">
+                  <el-table-column type="index" fixed="left" label="搴忓彿" width="60"></el-table-column>
+                  <el-table-column v-for="header in headers" :key="header.prop" :prop="header.prop" :label="header.label"
+                    :min-width="header.width" align="center">
+                    <template #default="scope">{{ scope.row[header.prop] }}</template>  
+                  </el-table-column>
+                  <el-table-column label="鎿嶄綔" fixed="right" width="240" align="center">
+                    <template #default="scope">
+                      <el-button type="warning" size="small" @click="goRt(scope.row)">瀹炴椂鐩戞祴</el-button>
+                      <el-button type="primary" size="small" @click="goHis(scope.row)">鍘嗗彶鏁版嵁</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-page">
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="getList" :icon="Search">鏌ヨ</el-button>
+            </div>
+            <div class="el-page-container">
+              <el-pagination v-model:current-page="pageCurr" v-model:page-size="pageSize"
+                :page-sizes="[20, 40, 60, 80, 100, 200, 300, 400]" size="small" :disabled="disabled"
+                :background="background" layout="total, sizes, prev, pager, next, jumper" :total="total"
+                @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+            </div>
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="exportExcel">瀵煎嚭</el-button>
+            </div>
+          </div>
+        </div>
+      </yc-card>
+    </div>
+    <div class="page-footer"></div>
+  </div>
+</template>
+
+<style scoped lang="less">
+.page-wrapper {
+  display: flex;
+  flex-direction: row;
+  // padding: 8px;
+  height: 100%;
+
+  .page-content {
+    flex: 1;
+  }
+}
+
+.page-content-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .page-content-tools {
+    padding-bottom: 8px;
+  }
+
+  .page-content-table {
+    // border-top: 1px solid var(--border-light-color);
+    box-sizing: border-box;
+    flex: 1;
+    margin-left: 26px;
+    margin-right: 26px;
+  }
+
+  .page-content-page {
+    padding: 8px 8px 0 8px;
+    text-align: center;
+
+    .el-page-container {
+      display: inline-block;
+      padding: 0 16px;
+    }
+
+    .page-tool {
+      display: inline-block;
+    }
+  }
+}
+
+.hdw-card-container {
+  width: 240px;
+  padding-right: 8px;
+  height: 100%;
+}
+
+.pos-rel {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .pos-abs {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.tools-filter {
+  display: inline-block;
+  font-size: 14px;
+
+  .tools-filter-item {
+    display: inline-block;
+    margin-right: 8px;
+
+    .filter-label {
+      display: inline-block;
+    }
+
+    .filter-content {
+      display: inline-block;
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/statistics/power.vue b/src/views/statistics/power.vue
new file mode 100644
index 0000000..6f16465
--- /dev/null
+++ b/src/views/statistics/power.vue
@@ -0,0 +1,516 @@
+<script setup name="power">
+	import { ref, reactive, onMounted, computed } from "vue";
+	import { storeToRefs } from "pinia";
+	import { Search, Plus } from "@element-plus/icons-vue";
+	import ycCard from "@/components/ycCard/index.vue";
+	import addEdit from "../datas/addEdit.vue";
+	import { ElMessage } from "element-plus";
+	import useElement from "@/hooks/useElement.js";
+  import { useUserStore } from '@/store/user';
+
+  import useStation from "@/hooks/useStationList.js";
+  const { provice, city, country, stationName,
+    proviceList, cityList, countryList, stationList,
+  } = useStation();
+
+  import powerTypes from '@/utils/const/const_powerType.js';
+  import {
+    delBatt,
+  } from "@/api/station";
+
+  const userStore = useUserStore();
+  const { uid, uname } = storeToRefs(userStore);
+
+
+
+  import {
+    getDevList,
+  } from "@/api/station";
+
+	const { $loading, $message, $confirm } = useElement();
+
+	const headers = [
+    {
+			prop: "provice",
+			label: "鐪�",
+			width: "80",
+		},
+    {
+			prop: "city",
+			label: "甯�",
+			width: "80",
+		},
+    {
+			prop: "country",
+			label: "鍖哄幙",
+			width: "80",
+		},
+		{
+			prop: "stationName",
+			label: "鏈烘埧鍚嶇О",
+			width: "160",
+		},
+    {
+			prop: "stationType",
+			label: "鐢靛帇绛夌骇",
+			width: "80",
+		},
+    {
+			prop: "longitude",
+			label: "缁忓害",
+			width: "80",
+		},
+    {
+			prop: "latitude",
+			label: "绾害",
+			width: "80",
+		},
+    {
+			prop: "powerName",
+			label: "鐢垫簮鍚嶇О",
+			width: "80",
+		},
+    {
+			prop: "powerTypeStr",
+			label: "鐢垫簮绫诲瀷",
+			width: "80",
+		},
+    {
+			prop: "company",
+			label: "鐢垫簮鍝佺墝",
+			width: "80",
+		},
+		{
+			prop: "powerModel",
+			label: "鐢垫簮鍨嬪彿",
+			width: "80",
+		},
+    {
+			prop: "protocol",
+			label: "鐢垫簮鍗忚",
+			width: "80",
+		},
+    {
+			prop: "powerIp",
+			label: "鐢垫簮IP",
+			width: "120",
+		},
+    {
+			prop: "devName",
+			label: "璁惧鍚嶇О",
+			width: "120",
+		},
+    {
+			prop: "devType",
+			label: "璁惧鍨嬪彿",
+			width: "120",
+		},
+    {
+			prop: "devIp",
+			label: "璁惧IP",
+			width: "120",
+		},
+    {
+			prop: "battgroupName",
+			label: "鐢垫睜缁勫悕绉�",
+			width: "120",
+		},
+    {
+			prop: "moncount",
+			label: "鐢垫睜鍗曚綋涓暟",
+			width: "120",
+		},
+    {
+			prop: "monvolstd",
+			label: "鐢垫睜鏍囩О鐢靛帇",
+			width: "120",
+		},
+    {
+			prop: "moncapstd",
+			label: "鐢垫睜鏍囩О瀹归噺",
+			width: "120",
+		},
+    {
+			prop: "monresstd",
+			label: "鐢垫睜鏍囩О鍐呴樆",
+			width: "120",
+		},
+    {
+			prop: "product",
+			label: "鐢垫睜鍝佺墝",
+			width: "120",
+		},
+    {
+			prop: "battModel",
+			label: "鐢垫睜鍨嬪彿",
+			width: "120",
+		},
+	];
+	const background = ref(true);
+	const disabled = ref(false);
+	const pageCurr = ref(1);
+	const pageSize = ref(10);
+	const total = ref(0);
+	const addEditVisible = ref(false);
+	const dialogTitle = ref("");
+	const currentAreaId = ref();
+	const currentAreaIds = ref([]);
+	const datas = reactive({
+		tableData: [],
+		rowData: {},
+	});
+
+	// const tableData = reactive([]);
+	// const rowData = reactive({});
+
+	// const userStore = useUserStore();
+	// const { uid, uname } = storeToRefs(userStore);
+
+	function getList() {
+		let loading = $loading();
+    let params = {
+      // city: "",
+      // country: "",
+      pageNum: pageCurr.value,
+      pageSize: pageSize.value,
+      // powerName: "",
+      // provice: "",
+      // stationName: "",
+    };
+
+		getDevList(params)
+			.then((res) => {
+				let { code, data, data2 } = res;
+				let list = [];
+				let _total = 0;
+				if (code && data) {
+					// console.log(data);
+					list = data2.list.map(v => ({
+            ...v,
+            powerTypeStr: powerTypes[v.powerType],
+            // roleName: roles[v.role],
+          }));
+					_total = data2.total;
+				}
+				loading.close();
+				// tableData.length = 0;
+				// tableData.push(...list);
+				datas.tableData = list;
+				total.value = _total;
+			})
+			.catch((err) => {
+				console.log(err);
+				loading.close();
+			});
+	}
+
+	// 灞曠ず鏁版嵁鏁伴噺
+	function handleSizeChange(val) {
+		pageSize.value = val;
+		getList();
+	}
+	// 缈婚〉
+	function handleCurrentChange(val) {
+		pageCurr.value = val;
+		getList();
+	}
+	function add() {
+		dialogTitle.value = "娣诲姞璁惧";
+		datas.rowData = {};
+		addEditVisible.value = true;
+	}
+	function edit(record) {
+		dialogTitle.value = "缂栬緫璁惧";
+		addEditVisible.value = true;
+		console.log(record, '=======edit======');
+		// if (record.ainfList.idPath) {
+		// 	ids = record.ainfList.idPath.split("_").map((v) => v * 1);
+		// }
+		// ids.push(record.areaId);
+
+		datas.rowData = { ...record };
+	}
+
+  function addBatt(record) {
+    dialogTitle.value = "娣诲姞鐢垫睜缁�";
+		datas.rowData = { ...record, addBattFlag: true };
+		addEditVisible.value = true;
+  }
+  
+	function confirmRemove(record) {
+		$confirm("鍒犻櫎", () => {
+      let { stationId, powerId, battgroupId } = record;
+			remove(stationId, powerId, battgroupId||undefined);
+		});
+	}
+	function remove(stationId, powerId, battgroupId) {
+		let loading = $loading();
+		delBatt(stationId, powerId, battgroupId)
+			.then((res) => {
+				let { code, data } = res;
+				loading.close();
+				if (code && data) {
+					$message.success("鎿嶄綔鎴愬姛");
+					handleCurrentChange(1);
+				} else {
+					$message.success("鎿嶄綔澶辫触");
+				}
+			})
+			.catch((err) => {
+				loading.close();
+				console.log(err);
+			});
+	}
+	function onOk() {
+		addEditVisible.value = false;
+		handleCurrentChange(1);
+	}
+	function onCanel() {
+		addEditVisible.value = false;
+	}
+
+
+	onMounted(() => {
+		getList();
+	});
+</script>
+
+<template>
+  <div class="page-wrapper">
+    <!-- <div class="page-header">
+    </div> -->
+    <div class="page-content">
+      <yc-card is-full>
+        <div class="page-content-wrapper">
+          <div class="page-content-tools page-filter">
+            <div class="table-row">
+              <div class="table-cell text-right">鐪侊細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="provice"
+                  size="small"
+                  placeholder="璇烽�夋嫨鐪�"
+                >
+                  <el-option
+                    v-for="item in proviceList"
+                    :key="'l0_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">甯傦細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="city"
+                  size="small"
+                  placeholder="璇烽�夋嫨甯�"
+                >
+                  <el-option
+                    v-for="item in cityList"
+                    :key="'l1_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鍖哄幙:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="country"
+                  size="small"
+                  placeholder="璇烽�夋嫨鍖哄幙"
+                >
+                  <el-option
+                    v-for="item in countryList"
+                    :key="'l2_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">绔欑偣:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="stationName"
+                  size="small"
+                  placeholder="璇烽�夋嫨绔欑偣"
+                >
+                  <el-option
+                    v-for="item in stationList"
+                    :key="'l3_' + item.stationId"
+                    :label="item.stationName"
+                    :value="item.stationName"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鍝佺墝:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="country"
+                  size="small"
+                  placeholder="璇烽�夋嫨鍝佺墝"
+                >
+                  <el-option
+                    v-for="item in countryList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鐢靛帇绛夌骇:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="country"
+                  size="small"
+                  placeholder="璇烽�夋嫨鐢靛帇绛夌骇"
+                >
+                  <el-option
+                    v-for="item in countryList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-table">
+            <div class="pos-rel">
+              <div class="pos-abs">
+                <el-table class="yc-table" stripe height="100%" size="small" :data="datas.tableData" style="width: 100%">
+                  <el-table-column type="index" fixed="left" label="搴忓彿" width="60"></el-table-column>
+                  <el-table-column v-for="header in headers" :key="header.prop" :prop="header.prop" :label="header.label"
+                    :min-width="header.width" align="center">
+                    <template #default="scope">{{ scope.row[header.prop] || '--' }}</template>  
+                  </el-table-column>
+                  <el-table-column label="鎿嶄綔" fixed="right" width="240" align="center">
+                    <template #default="scope">
+                      <el-button type="primary" size="small"
+                        @click="edit(scope.row)">缂栬緫</el-button>
+                      <el-button type="danger" size="small" 
+                        @click="confirmRemove(scope.row)">鍒犻櫎</el-button>
+                      <el-button type="primary" size="small" 
+                        @click="addBatt(scope.row)">娣诲姞鐢垫睜缁�</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-page">
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="add" :icon="Plus">娣诲姞</el-button>
+              <el-button type="primary" round size="small" @click="getList" :icon="Search">鏌ヨ</el-button>
+            </div>
+            <div class="el-page-container">
+              <el-pagination v-model:current-page="pageCurr" v-model:page-size="pageSize"
+                :page-sizes="[20, 40, 60, 80, 100, 200, 300, 400]" size="small" :disabled="disabled"
+                :background="background" layout="total, sizes, prev, pager, next, jumper" :total="total"
+                @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+            </div>
+            <div class="page-tool"></div>
+          </div>
+        </div>
+      </yc-card>
+    </div>
+    <div class="page-footer"></div>
+    <!-- 寮圭獥 -->
+    <el-dialog :title="dialogTitle" v-model="addEditVisible" top="0" :close-on-click-modal="false" class="dialog-center"
+      width="860px" center>
+      <add-edit v-if="addEditVisible" @success="onOk" :info="datas.rowData" @cancel="onCanel"></add-edit>
+    </el-dialog>
+  </div>
+</template>
+
+<style scoped lang="less">
+.page-wrapper {
+  display: flex;
+  flex-direction: row;
+  // padding: 8px;
+  height: 100%;
+
+  .page-content {
+    flex: 1;
+  }
+}
+
+.page-content-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .page-content-tools {
+    padding-bottom: 8px;
+  }
+
+  .page-content-table {
+    // border-top: 1px solid var(--border-light-color);
+    box-sizing: border-box;
+    flex: 1;
+    margin-left: 26px;
+    margin-right: 26px;
+  }
+
+  .page-content-page {
+    padding: 8px 8px 0 8px;
+    text-align: center;
+
+    .el-page-container {
+      display: inline-block;
+      padding: 0 16px;
+    }
+
+    .page-tool {
+      display: inline-block;
+    }
+  }
+}
+
+.hdw-card-container {
+  width: 240px;
+  padding-right: 8px;
+  height: 100%;
+}
+
+.pos-rel {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .pos-abs {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.tools-filter {
+  display: inline-block;
+  font-size: 14px;
+
+  .tools-filter-item {
+    display: inline-block;
+    margin-right: 8px;
+
+    .filter-label {
+      display: inline-block;
+    }
+
+    .filter-content {
+      display: inline-block;
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/statistics/station.vue b/src/views/statistics/station.vue
new file mode 100644
index 0000000..034a153
--- /dev/null
+++ b/src/views/statistics/station.vue
@@ -0,0 +1,421 @@
+<script setup name="station">
+	import { ref, reactive, onMounted, computed, nextTick } from "vue";
+	import { storeToRefs } from "pinia";
+	import { Search, Plus } from "@element-plus/icons-vue";
+	import ycCard from "@/components/ycCard/index.vue";
+	import addEdit from "../datas/addEdit.vue";
+	import { ElMessage } from "element-plus";
+	import useElement from "@/hooks/useElement.js";
+  import { useUserStore } from '@/store/user';
+
+  import useStation from "@/hooks/useStationList.js";
+  const { provice, city, country, stationName,
+    proviceList, cityList, countryList, stationList,
+  } = useStation();
+
+  import { useRouter } from "vue-router";
+  const router = useRouter();
+
+  import { ExportFile } from '@/utils/exportFile.js';
+
+
+  import powerTypes from '@/utils/const/const_powerType.js';
+  import {
+    delBatt,
+  } from "@/api/station";
+
+  const userStore = useUserStore();
+  const { uid, uname } = storeToRefs(userStore);
+
+
+  import {
+    getVoltageLevel,
+  } from "@/api/station";
+
+  import {
+    getStationStatistic,
+  } from "@/api/statistic.js";
+
+	const { $loading, $message, $confirm } = useElement();
+
+	const headers = [
+    {
+			prop: "provice",
+			label: "鐪�",
+			width: "80",
+		},
+    {
+			prop: "city",
+			label: "甯�",
+			width: "80",
+		},
+    {
+			prop: "country",
+			label: "鍖哄幙",
+			width: "80",
+		},
+		{
+			prop: "stationName",
+			label: "鏈烘埧鍚嶇О",
+			width: "160",
+		},
+    {
+			prop: "stationType",
+			label: "鐢靛帇绛夌骇",
+			width: "80",
+		},
+    {
+			prop: "longitude",
+			label: "缁忓害",
+			width: "80",
+		},
+    {
+			prop: "latitude",
+			label: "绾害",
+			width: "80",
+		},
+	];
+
+  const volLevels = ref([]);
+  const stationType = ref('');
+	const background = ref(true);
+	const disabled = ref(false);
+	const pageCurr = ref(1);
+	const pageSize = ref(10);
+	const total = ref(0);
+	const addEditVisible = ref(false);
+	const dialogTitle = ref("");
+	const currentAreaId = ref();
+	const currentAreaIds = ref([]);
+	const datas = reactive({
+		tableData: [],
+		rowData: {},
+	});
+
+	// const tableData = reactive([]);
+	// const rowData = reactive({});
+
+	// const userStore = useUserStore();
+	// const { uid, uname } = storeToRefs(userStore);
+
+  // 鑾峰彇鐢靛帇绛夌骇
+  function getVolLevels() {
+    console.log("鑾峰彇鐢靛帇绛夌骇");
+    getVoltageLevel().then((res) => {
+      let { code, data, data2 } = res;
+      let list = [];
+      if (code && data) {
+        list = data2;
+      }
+      volLevels.value = list;
+    });
+  }
+
+	function getList() {
+		let loading = $loading();
+    let params = {
+      provice: provice.value || undefined,
+      city: city.value || undefined,
+      country: country.value || undefined,
+      stationName: stationName.value || undefined,
+      stationType: stationType.value || undefined,
+      pageNum: pageCurr.value,
+      pageSize: pageSize.value,
+    };
+
+		getStationStatistic(params)
+			.then((res) => {
+				let { code, data, data2 } = res;
+				let list = [];
+				let _total = 0;
+				if (code && data) {
+					// console.log(data);
+					list = data2.list.map(v => ({
+            ...v,
+          }));
+					_total = data2.total;
+				}
+				loading.close();
+				// tableData.length = 0;
+				// tableData.push(...list);
+				datas.tableData = list;
+				total.value = _total;
+			})
+			.catch((err) => {
+				console.log(err);
+				loading.close();
+			});
+	}
+
+	// 灞曠ず鏁版嵁鏁伴噺
+	function handleSizeChange(val) {
+		pageSize.value = val;
+		getList();
+	}
+	// 缈婚〉
+	function handleCurrentChange(val) {
+		pageCurr.value = val;
+		getList();
+	}
+
+  function filterChange() {
+    nextTick(() => {
+      pageCurr.value = 1;
+      getList();
+    });
+  }
+
+  function goRt (row) {
+    router.push({
+      path: '/datas/realtime',
+      query: {
+        stationId: row.stationId,
+        pageFlag: Math.random(),
+      }
+    });
+  }
+
+  function goHis (row) {
+    router.push({
+      path: '/datas/realtime',
+      query: {
+        id: row.battgroupId
+      }
+    });
+  }
+
+  function exportExcel() {
+    ExportFile(headers, datas.tableData, "绔欑偣缁熻");
+  }
+
+	onMounted(() => {
+    getVolLevels();
+		getList();
+	});
+</script>
+
+<template>
+  <div class="page-wrapper">
+    <!-- <div class="page-header">
+    </div> -->
+    <div class="page-content">
+      <yc-card is-full>
+        <div class="page-content-wrapper">
+          <div class="page-content-tools page-filter">
+            <div class="table-row">
+              <div class="table-cell text-right">鐪侊細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="provice"
+                  size="small"
+                  clearable
+                  @change="filterChange"
+                  placeholder="璇烽�夋嫨鐪�"
+                >
+                  <el-option
+                    v-for="item in proviceList"
+                    :key="'l0_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">甯傦細</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="city"
+                  size="small"
+                  clearable
+                  @change="filterChange"
+                  placeholder="璇烽�夋嫨甯�"
+                >
+                  <el-option
+                    v-for="item in cityList"
+                    :key="'l1_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鍖哄幙:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="country"
+                  clearable
+                  @change="filterChange"
+                  size="small"
+                  placeholder="璇烽�夋嫨鍖哄幙"
+                >
+                  <el-option
+                    v-for="item in countryList"
+                    :key="'l2_' + item"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">绔欑偣:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="stationName"
+                  clearable
+                  @change="filterChange"
+                  size="small"
+                  placeholder="璇烽�夋嫨绔欑偣"
+                >
+                  <el-option
+                    v-for="item in stationList"
+                    :key="'l3_' + item.stationId"
+                    :label="item.stationName"
+                    :value="item.stationName"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="table-cell text-right">鐢靛帇绛夌骇:</div>
+              <div class="table-cell">
+                <el-select
+                  v-model="stationType"
+                  size="small"
+                  clearable
+                  @change="filterChange"
+                  placeholder="璇烽�夋嫨鐢靛帇绛夌骇"
+                >
+                  <el-option
+                    v-for="(item, idx) in volLevels"
+                    :key="'list5_' + idx"
+                    :label="item"
+                    :value="item"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-table">
+            <div class="pos-rel">
+              <div class="pos-abs">
+                <el-table class="yc-table" stripe height="100%" size="small" :data="datas.tableData" style="width: 100%">
+                  <el-table-column type="index" fixed="left" label="搴忓彿" width="60"></el-table-column>
+                  <el-table-column v-for="header in headers" :key="header.prop" :prop="header.prop" :label="header.label"
+                    :min-width="header.width" align="center">
+                    <template #default="scope">{{ scope.row[header.prop] || '--' }}</template>  
+                  </el-table-column>
+                  <el-table-column label="鎿嶄綔" fixed="right" width="240" align="center">
+                    <template #default="scope">
+                      <el-button type="warning" size="small" @click="goRt(scope.row)">瀹炴椂鐩戞祴</el-button>
+                      <el-button type="primary" size="small" @click="goHis(scope.row)">鍘嗗彶鏁版嵁</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </div>
+            </div>
+          </div>
+          <div class="page-content-page">
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="getList" :icon="Search">鏌ヨ</el-button>
+            </div>
+            <div class="el-page-container">
+              <el-pagination v-model:current-page="pageCurr" v-model:page-size="pageSize"
+                :page-sizes="[20, 40, 60, 80, 100, 200, 300, 400]" size="small" :disabled="disabled"
+                :background="background" layout="total, sizes, prev, pager, next, jumper" :total="total"
+                @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+            </div>
+            <div class="page-tool">
+              <el-button type="primary" round size="small" @click="exportExcel">瀵煎嚭</el-button>
+            </div>
+          </div>
+        </div>
+      </yc-card>
+    </div>
+    <div class="page-footer"></div>
+  </div>
+</template>
+
+<style scoped lang="less">
+.page-wrapper {
+  display: flex;
+  flex-direction: row;
+  // padding: 8px;
+  height: 100%;
+
+  .page-content {
+    flex: 1;
+  }
+}
+
+.page-content-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+
+  .page-content-tools {
+    padding-bottom: 8px;
+  }
+
+  .page-content-table {
+    // border-top: 1px solid var(--border-light-color);
+    box-sizing: border-box;
+    flex: 1;
+    margin-left: 26px;
+    margin-right: 26px;
+  }
+
+  .page-content-page {
+    padding: 8px 8px 0 8px;
+    text-align: center;
+
+    .el-page-container {
+      display: inline-block;
+      padding: 0 16px;
+    }
+
+    .page-tool {
+      display: inline-block;
+    }
+  }
+}
+
+.hdw-card-container {
+  width: 240px;
+  padding-right: 8px;
+  height: 100%;
+}
+
+.pos-rel {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .pos-abs {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.tools-filter {
+  display: inline-block;
+  font-size: 14px;
+
+  .tools-filter-item {
+    display: inline-block;
+    margin-right: 8px;
+
+    .filter-label {
+      display: inline-block;
+    }
+
+    .filter-content {
+      display: inline-block;
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/views/user/MyCard.vue b/src/views/user/MyCard.vue
index cf03cb5..d671b19 100644
--- a/src/views/user/MyCard.vue
+++ b/src/views/user/MyCard.vue
@@ -1,8 +1,7 @@
 <template>
     <div class="my-card">
         <div class="my-card-header">
-            <div class="card-title">
-                <img src="@/assets/images/card-title.png" class="card-title-img">
+            <div class="card-title title ys-title panel-title">
                 {{title}}
             </div>
             <div class="my-card-tools">
@@ -46,7 +45,7 @@
 }
 .my-card-header {
     display: flex;
-    font-size: 16px;
+    font-size: 20px;
     margin: 8px;
     padding: 8px;
 }
@@ -55,9 +54,9 @@
     margin-right: 4px;
 }
 .card-title {
-    color: #00feff;
-    font-weight: bold;
-    line-height: 32px;
+    color: #fff;
+    font-weight: normal;
+    line-height: 26px;
 }
 .my-card-tools {
     flex: 1;
diff --git a/src/views/user/baojiMager.vue b/src/views/user/baojiMager.vue
index 96226f8..5ac22a1 100644
--- a/src/views/user/baojiMager.vue
+++ b/src/views/user/baojiMager.vue
@@ -550,12 +550,12 @@
 
 <style scoped lang="less">
 .page-wrapper {
-  padding: 10px;
+  padding: 10px 28px;
 }
 :deep(.flex-layout-body) {
   margin-left: 10px;
-  border: 1px solid #0ff;
-  border-radius: 6px;
+  border: 1px solid #1B4C79;
+  border-radius: 0;
 }
 :deep(.el-dialog__body .flex-layout-body) {
   margin-left: 0;
@@ -701,10 +701,10 @@
   }
 }
 .flex-page-content {
-  margin-left: 8px;
-  margin-right: 8px;
+  // margin-left: 8px;
+  // margin-right: 8px;
   height: 100%;
-  padding: 0 8px;
+  // padding: 0 8px;
   :deep(.el-tabs) {
     height: 100%;
   }
diff --git a/src/views/user/index.vue b/src/views/user/index.vue
index 47d6576..b8c6e2a 100644
--- a/src/views/user/index.vue
+++ b/src/views/user/index.vue
@@ -216,7 +216,7 @@
 </script>
 
 <template>
-  <div class="page-wrapper">
+  <div class="page-wrapper page-contain bg-footer">
     <!-- <div class="page-header">
     </div> -->
     <div class="page-content">
@@ -355,7 +355,7 @@
 .page-wrapper {
   display: flex;
   flex-direction: row;
-  padding: 8px;
+  padding: 8px 32px 42px;
   height: 100%;
 
   .page-content {
diff --git a/src/views/user/powerMager.vue b/src/views/user/powerMager.vue
index 071f908..541bbbd 100644
--- a/src/views/user/powerMager.vue
+++ b/src/views/user/powerMager.vue
@@ -43,7 +43,7 @@
       </my-card>
     </template>
     <div class="flex-page-content">
-      <context-box title="鐢ㄦ埛-鏉冮檺鏍�">
+      <context-box title="鐢ㄦ埛鏉冮檺鏍�">
         <div class="power-user-tree">
           <div class="power-content power-content-user">
             <el-transfer
@@ -870,7 +870,7 @@
 
 <style scoped lang="less">
 .page-wrapper {
-  padding: 10px;
+  padding: 10px 28px;
   :deep(.flex-layout-body) {
     margin-left: 10px;
     border: 1px solid #38628E;
@@ -879,10 +879,12 @@
     }
     // border-radius: 6px;
     .content-box-title {
+      font-family: 'YouSheBiaoTiHei';
       background-color: #014886;
       margin: 0;
       border-radius: 0;
       color: #fff;
+      font-weight: normal;
     }
   }
 }

--
Gitblit v1.9.1