index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. <template>
  2. <div class="data-search">
  3. <div class="leftContent">
  4. <span>{{ pageTitle }}</span>
  5. </div>
  6. <div class="data-content">
  7. <div class="card-left">
  8. <div class="cardleft">
  9. <div class="cardleft-search">
  10. <div class="cardleft_top">
  11. <el-select
  12. v-model="state.stationId"
  13. clearable
  14. size="mini"
  15. placeholder="全部场站"
  16. popper-class="select"
  17. @change="getWindturbineList()"
  18. >
  19. <el-option
  20. v-for="item in stationList"
  21. :key="item.id"
  22. :value="item.id"
  23. :label="item.name"
  24. ></el-option>
  25. </el-select>
  26. <el-select
  27. v-model="state.modeId"
  28. clearable
  29. size="mini"
  30. placeholder="全部机型"
  31. popper-class="select"
  32. >
  33. <el-option
  34. v-for="item in modelList"
  35. :key="item.id"
  36. :value="item.id"
  37. :label="item.aname"
  38. ></el-option>
  39. </el-select>
  40. </div>
  41. <el-input
  42. v-model="state.input"
  43. size="mini"
  44. placeholder="设备检索"
  45. clearable
  46. />
  47. </div>
  48. <div style="height: calc(100% - 74px)">
  49. <el-scrollbar height="100%">
  50. <div
  51. v-for="item in filterWTList"
  52. :key="item"
  53. :class="[
  54. 'scrollbar-demo-item',
  55. item.id == state.activeWT ? 'active' : '',
  56. ]"
  57. @click="clickWT(item)"
  58. >
  59. {{ item.aname }}
  60. </div>
  61. </el-scrollbar>
  62. </div>
  63. </div>
  64. <div class="cardright">
  65. <el-input
  66. v-model="state.inputAI"
  67. style="margin-bottom: 10px"
  68. size="mini"
  69. placeholder="点名检索"
  70. clearable
  71. />
  72. <el-tree
  73. :data="state.treedata"
  74. :props="state.defaultProps"
  75. node-key="id"
  76. highlight-current
  77. default-expand-all
  78. @node-click="handleNodeClick"
  79. />
  80. </div>
  81. </div>
  82. <div class="card-right">
  83. <el-table
  84. :data="filterAIList"
  85. style="width: 100%"
  86. height="calc(100% - 40px)"
  87. >
  88. <el-table-column
  89. prop="uniformCode"
  90. label="点名"
  91. align="center"
  92. width="120"
  93. />
  94. <el-table-column prop="name" label="名称" align="left" />
  95. <el-table-column prop="value" label="值" width="120" align="right">
  96. <template #default="scope">
  97. <el-tag
  98. size="small"
  99. color="#133122"
  100. style="border-color: #67c23a"
  101. type="success"
  102. >
  103. {{ scope.row.value }}
  104. </el-tag>
  105. </template>
  106. </el-table-column>
  107. <el-table-column label="时间" width="180" align="center">
  108. <template #default="scope">
  109. {{ dayjs(scope.row.time).format("YYYY-MM-DD HH:mm:ss") }}
  110. </template>
  111. </el-table-column>
  112. <el-table-column label="单位" width="100" align="center">
  113. <template #default="scope">
  114. {{ scope.row.unitName }}
  115. {{ scope.row.unitNameCn ? `(${scope.row.unitNameCn})` : "--" }}
  116. </template>
  117. </el-table-column>
  118. <el-table-column width="100" align="center">
  119. <template #default="scope">
  120. <el-button
  121. type="text"
  122. style="color: #05bb4c"
  123. size="mini"
  124. @click="handleSearch(scope.row)"
  125. >历史数据</el-button
  126. >
  127. </template>
  128. </el-table-column>
  129. </el-table>
  130. <div class="pagination">
  131. <el-pagination
  132. layout="total,sizes, prev, pager, next"
  133. :current-page="query.page"
  134. :page-size="query.limit"
  135. :page-sizes="[23, 50, 100, 500]"
  136. :total="query.pageTotal"
  137. @current-change="handlePageChange"
  138. @size-change="
  139. (value) => {
  140. query.page = 1;
  141. query.limit = value;
  142. getFetchAIPointListt();
  143. }
  144. "
  145. ></el-pagination>
  146. </div>
  147. </div>
  148. <el-dialog
  149. v-model="dialogVisible"
  150. width="80%"
  151. :before-close="dialogClose"
  152. >
  153. <template #title>
  154. <div class="dialog-title">
  155. <div class="title">
  156. {{
  157. `${state.handleSearch?.name || ""}_${state.activeAI}历史数据`
  158. }}
  159. </div>
  160. </div>
  161. </template>
  162. <div class="pickerFifter">
  163. <el-date-picker
  164. size="mini"
  165. v-model="state.pickerVal"
  166. type="datetimerange"
  167. range-separator="-"
  168. start-placeholder="开始时间"
  169. end-placeholder="结束时间"
  170. >
  171. </el-date-picker>
  172. <el-select
  173. v-model="state.selectDataVal"
  174. placeholder="数据源"
  175. style="margin: 0 10px"
  176. >
  177. <el-option
  178. v-for="item in state.selectData"
  179. :key="item.value"
  180. :label="item.label"
  181. :value="item.value"
  182. >
  183. </el-option>
  184. </el-select>
  185. <!-- <el-select
  186. v-model="state.selectTimeVal"
  187. v-show="state.selectDataVal !== 1"
  188. placeholder="时间间隔"
  189. >
  190. <el-option
  191. v-for="item in state.selectTime"
  192. :key="item.value"
  193. :label="item.label"
  194. :value="item.value"
  195. >
  196. </el-option>
  197. </el-select> -->
  198. <el-input
  199. size="mini"
  200. class="selectTimeVal"
  201. v-model="state.selectTimeVal"
  202. style="width: 200px"
  203. v-show="state.selectDataVal !== 1"
  204. placeholder="时间间隔"
  205. >
  206. <template #append>分钟</template>
  207. </el-input>
  208. <el-button size="mini" class="buttons" round @click="pickerSearch"
  209. >查询</el-button
  210. >
  211. <el-button size="mini" class="buttons" round @click="export2Excel"
  212. >导出</el-button
  213. >
  214. </div>
  215. <div
  216. class="dChart"
  217. style="height: 500px; width: 100%"
  218. :id="eChart"
  219. ></div>
  220. </el-dialog>
  221. </div>
  222. </div>
  223. </template>
  224. <script setup>
  225. import { getApiequipmentListByWp } from "@/api/monthlyPerformanceAnalysis.js";
  226. import {
  227. ref,
  228. onMounted,
  229. watch,
  230. reactive,
  231. computed,
  232. onUnmounted,
  233. nextTick,
  234. } from "vue";
  235. import {
  236. fetchWindturbineList,
  237. tree,
  238. fetchAIDIPointList,
  239. getAdapterLatest,
  240. getAdapterHistory,
  241. getAdapterHistorysnap,
  242. AdapterHistoryExport,
  243. getStationinfo,
  244. fetchModel,
  245. } from "@/api/zhbj/index.js";
  246. import dayjs from "dayjs";
  247. import * as echarts from "echarts";
  248. import { useStore } from "vuex";
  249. import axios from "axios";
  250. import { ElMessage } from "element-plus";
  251. import { outExportExcel } from "@/tools/excel/exportExcel.js"; //引入文件
  252. const pageTitle = "数据查询";
  253. const store = useStore();
  254. const isStation = computed(() => store.getters.isStation);
  255. const stationList = computed(() => store.state.stationListAll);
  256. const eChart = ref("eChart" + Date.now() + Math.random());
  257. watch(
  258. () => stationList,
  259. (val, old) => {
  260. val.value[0] &&
  261. nextTick(async () => {
  262. state.stationId = stationList.value[0]?.id;
  263. await getWindturbineList();
  264. getTreeData();
  265. });
  266. },
  267. {
  268. deep: true,
  269. immediate: true,
  270. }
  271. );
  272. onMounted(async () => {
  273. getequipmentmodel_list();
  274. });
  275. onUnmounted(() => {
  276. state.echarts?.clear(); //销毁
  277. clearInterval(state.Interval);
  278. state.Interval = null;
  279. });
  280. const dialogClose = (done) => {
  281. state.echarts?.clear(); //销毁
  282. done();
  283. };
  284. const query = reactive({
  285. page: 1,
  286. limit: 23,
  287. pageTotal: null,
  288. });
  289. const dialogVisible = ref(null);
  290. const state = reactive({
  291. handleSearch: null,
  292. input: "",
  293. inputAI: "",
  294. stationId: "",
  295. windturbineList: [],
  296. modelListAll: [],
  297. modeId: "",
  298. AIList: [],
  299. activeWT: "",
  300. activeAI: "",
  301. Interval: null,
  302. pickerVal: [new Date(new Date() - 1 * 60 * 60 * 1000), new Date()],
  303. echarts: null,
  304. treedata: [],
  305. defaultProps: {
  306. children: "children",
  307. label: (a) => {
  308. return a.node.name;
  309. },
  310. },
  311. structcode: "",
  312. selectDataVal: 1,
  313. selectData: [
  314. {
  315. label: "原始数据",
  316. value: 1,
  317. },
  318. {
  319. label: "数据快照",
  320. value: 2,
  321. },
  322. ],
  323. selectTimeVal: 1,
  324. downData: null,
  325. });
  326. // 机型
  327. const getequipmentmodel_list = async () => {
  328. const { data } = await fetchModel();
  329. state.modelListAll = data;
  330. };
  331. const modelList = computed(() => {
  332. if (state.stationId == "") {
  333. return [];
  334. } else {
  335. return state.modelListAll[state.stationId];
  336. }
  337. });
  338. //get 风机机组
  339. const getWindturbineList = async () => {
  340. const { data } = await getApiequipmentListByWp({ wpid: state.stationId });
  341. state.windturbineList = data.data;
  342. state.activeWT = state.windturbineList[0]?.id;
  343. state.modeId = "";
  344. // await getequipmentmodel_list();
  345. await getFetchAIPointListt();
  346. };
  347. // 风机搜 索
  348. const filterWTList = computed(() =>
  349. state.windturbineList?.filter(
  350. (data) =>
  351. data.aname.includes(state.input) && data.modelId.includes(state.modeId)
  352. )
  353. );
  354. //AI点检索
  355. const filterAIList = computed(() =>
  356. // state.AIList?.filter(
  357. // (data) => !state.inputAI || data.name.includes(state.inputAI)
  358. // // !state.inputAI ||
  359. // // (data.uniformCode.toUpperCase().includes(state.inputAI.toUpperCase()) ||
  360. // // data.name.indexOf(state.inputAI))
  361. // )
  362. // );
  363. new Array(5).fill({
  364. unitNameCn: "",
  365. englishName: null,
  366. unitName: "",
  367. modelId: "SEC-W02B-1250kW",
  368. name: "01号风机风向",
  369. valueUnit: null,
  370. model: "SEC-W02B-1250kW",
  371. typeId: "F",
  372. id: "e222918e1e6a4feeba41007be3c85bf7",
  373. uniformCode: "AI067",
  374. value: "344.00",
  375. time: 1718176440000,
  376. })
  377. );
  378. // get 实时data
  379. const getLatest = async (stationId, AIlist) => {
  380. const data = await getAdapterLatest(stationId, AIlist, state.stationId);
  381. state.AIList?.forEach((e) => {
  382. if (e.uniformCode.indexOf("AI") !== -1) {
  383. e.value = Number(data[e.uniformCode]?.doubleValue).toFixed(2);
  384. }
  385. // else {
  386. // e.value = data[e.uniformCode]?.booleanValue - 0;
  387. // }
  388. e["time"] = data[e.uniformCode]?.ts;
  389. });
  390. };
  391. // get 历史数据
  392. const getHistory = async (stationId, AIpoint) => {
  393. let startTs = dayjs(state.pickerVal[0]).valueOf();
  394. let endTs = dayjs(state.pickerVal[1]).valueOf();
  395. const baseUrl = `http://10.81.3.162:801${
  396. /FDC/.test(state.stationId) ? "1" : "2"
  397. }/`;
  398. const data = await getAdapterHistory(
  399. stationId,
  400. AIpoint,
  401. startTs,
  402. endTs,
  403. baseUrl
  404. );
  405. state.downData = data;
  406. option.xAxis.data = [];
  407. option.series[0].data = [];
  408. let timeArr = [];
  409. data.forEach((e) => {
  410. option.series[0].data.push(e.doubleValue.toFixed(2));
  411. option.xAxis.data.push(dayjs(e.ts).format("YYYY-MM-DD HH:mm:ss"));
  412. });
  413. let chat = echarts.init(document.getElementById(eChart.value));
  414. state.echarts = await chat.setOption(option);
  415. };
  416. // get 数据快照
  417. const getHistorysnap = async (stationId, AIpoint, interval) => {
  418. let startTs = dayjs(state.pickerVal[0]).valueOf();
  419. let endTs = dayjs(state.pickerVal[1]).valueOf();
  420. const baseUrl = `http://10.81.3.162:801${
  421. /FDC/.test(state.stationId) ? "1" : "2"
  422. }/`;
  423. const data = await getAdapterHistorysnap(
  424. stationId,
  425. AIpoint,
  426. startTs,
  427. endTs,
  428. interval,
  429. baseUrl
  430. );
  431. state.downData = data;
  432. option.xAxis.data = [];
  433. option.series[0].data = [];
  434. let timeArr = [];
  435. data.forEach((e) => {
  436. option.series[0].data.push(e.doubleValue.toFixed(2));
  437. option.xAxis.data.push(dayjs(e.ts).format("YYYY-MM-DD HH:mm:ss"));
  438. });
  439. let chat = echarts.init(document.getElementById(eChart.value));
  440. state.echarts = await chat.setOption(option);
  441. };
  442. const option = {
  443. tooltip: {
  444. trigger: "axis",
  445. position: function (pt) {
  446. return [pt[0], "10%"];
  447. },
  448. },
  449. xAxis: {
  450. type: "category",
  451. boundaryGap: false,
  452. data: [],
  453. },
  454. yAxis: {
  455. type: "value",
  456. boundaryGap: [0, "100%"],
  457. },
  458. dataZoom: [
  459. {
  460. type: "inside",
  461. start: 0,
  462. end: 10,
  463. },
  464. {
  465. start: 0,
  466. end: 10,
  467. },
  468. ],
  469. series: [
  470. {
  471. data: [],
  472. type: "line",
  473. smooth: true,
  474. },
  475. ],
  476. };
  477. //get AI
  478. const getFetchAIPointListt = async () => {
  479. const res = await fetchAIDIPointList(
  480. "windturbine",
  481. state.structcode,
  482. state.stationId,
  483. state.activeWT,
  484. query.page,
  485. query.limit
  486. );
  487. state.AIList = res?.records;
  488. query.pageTotal = res?.total;
  489. let AIlist = [];
  490. res.records?.forEach((e) => {
  491. AIlist.push(e.uniformCode);
  492. });
  493. if (state.Interval) {
  494. clearInterval(state.Interval);
  495. }
  496. getLatest(state.activeWT, AIlist);
  497. state.Interval = setInterval(getLatest, 5000, state.activeWT, AIlist);
  498. };
  499. // TREE
  500. // tree click
  501. const handleNodeClick = (data) => {
  502. state.structcode = data.node.code;
  503. getFetchAIPointListt();
  504. };
  505. // gettreeData
  506. const getTreeData = async () => {
  507. // const res = await tree();
  508. // let data = res.children[1].children;
  509. let data = [];
  510. data.unshift({ node: { code: "", name: "全部" } });
  511. // data.unshift({ node: { code: "1010102", name: "全部" } });
  512. // data.forEach((e) => {
  513. // delete e.children;
  514. // });
  515. state.treedata = data;
  516. };
  517. //
  518. const handleSearch = (item) => {
  519. dialogVisible.value = true;
  520. state.handleSearch = item;
  521. state.activeAI = item.uniformCode;
  522. getHistory(state.activeWT, state.activeAI);
  523. };
  524. const clickWT = (item) => {
  525. state.activeWT = item.id;
  526. if (state.Interval) {
  527. clearInterval(state.Interval);
  528. }
  529. getFetchAIPointListt();
  530. };
  531. const pickerSearch = () => {
  532. if (state.selectDataVal == 1) {
  533. getHistory(state.activeWT, state.activeAI);
  534. } else {
  535. getHistorysnap(state.activeWT, state.activeAI, state.selectTimeVal * 60);
  536. }
  537. };
  538. // 分页导航
  539. const handlePageChange = (val) => {
  540. query.page = val;
  541. getFetchAIPointListt();
  542. };
  543. // 批量导出
  544. const export2Excel = async () => {
  545. let data = state.downData;
  546. data.forEach((e) => {
  547. e.doubleValue = e.doubleValue.toFixed(2);
  548. e.ts = dayjs(e.ts).format("YYYY-MM-DD HH:mm:ss");
  549. });
  550. ElMessage.success(`导出成功!`);
  551. const tableHeader = ["时间", "值"];
  552. const tableKey = ["ts", "doubleValue"];
  553. outExportExcel(
  554. tableHeader,
  555. tableKey,
  556. data,
  557. `${state.activeWT}_${state.activeAI}_${dayjs(state.pickerVal[0]).format(
  558. "YYYY-MM-DD HH:mm"
  559. )}-${dayjs(state.pickerVal[1]).format("YYYY-MM-DD HH:mm")}历史数据excel`
  560. );
  561. };
  562. </script>
  563. <style lang="less" scoped>
  564. .data-search {
  565. width: 100%;
  566. height: 100%;
  567. padding: 10px 20px;
  568. .leftContent {
  569. width: 242px;
  570. height: 41px;
  571. display: flex;
  572. align-items: center;
  573. background: url("~@/assets/imgs/title_left_bg1.png") no-repeat;
  574. span {
  575. font-size: 16px;
  576. font-family: Microsoft YaHei;
  577. font-weight: 400;
  578. color: #05bb4c;
  579. margin-left: 25px;
  580. }
  581. }
  582. .data-content {
  583. width: 100%;
  584. height: calc(100% - 40px);
  585. display: flex;
  586. justify-content: space-between;
  587. .card-left {
  588. width: 45%;
  589. height: 100%;
  590. display: flex;
  591. margin-right: 20px;
  592. .cardleft {
  593. width: 50%;
  594. height: 100%;
  595. margin-right: 20px;
  596. padding: 10px;
  597. background: #161f1e;
  598. .cardleft-search {
  599. padding-bottom: 10px;
  600. .cardleft_top {
  601. display: flex;
  602. padding-bottom: 10px;
  603. .el-select {
  604. flex: 1;
  605. &:nth-child(1) {
  606. margin-right: 10px;
  607. }
  608. }
  609. }
  610. }
  611. }
  612. .cardright {
  613. width: calc(50% - 20px);
  614. height: 100%;
  615. padding: 10px;
  616. background: #161f1e;
  617. }
  618. }
  619. .card-right {
  620. width: calc(55% - 20px);
  621. height: 100%;
  622. padding: 10px;
  623. background: #161f1e;
  624. .pagination {
  625. padding-top: 10px;
  626. }
  627. }
  628. }
  629. }
  630. .pickerFifter {
  631. width: 80%;
  632. margin: 0 auto;
  633. display: flex;
  634. align-items: center;
  635. margin-bottom: 10px;
  636. }
  637. .scrollbar-demo-item {
  638. display: flex;
  639. align-items: center;
  640. justify-content: center;
  641. cursor: pointer;
  642. height: 30px;
  643. margin: 8px 0;
  644. text-align: center;
  645. border-radius: 4px;
  646. color: #b3b3b3;
  647. background-color: rgba(7, 122, 52, 0.2);
  648. border: 1px solid #3b6c53;
  649. &.active,
  650. &:hover {
  651. background-color: rgba(5, 187, 76, 0.5);
  652. color: #ffffff;
  653. }
  654. &:nth-child(1) {
  655. margin-top: 0;
  656. }
  657. &:nth-last-child(1) {
  658. margin-bottom: 0;
  659. }
  660. }
  661. .buttons {
  662. background-color: rgba(5, 187, 76, 0.2);
  663. border: 1px solid #3b6c53;
  664. color: #b3b3b3;
  665. font-size: 14px;
  666. &:hover {
  667. background-color: rgba(5, 187, 76, 0.5);
  668. color: #ffffff;
  669. }
  670. }
  671. .selectTimeVal ::v-deep {
  672. margin-right: 10px;
  673. .el-input__inner {
  674. text-align: right;
  675. border-radius: 12.5px 0px 0px 12.5px !important;
  676. }
  677. .el-input-group__append {
  678. background-color: #172422 !important;
  679. color: #fff !important;
  680. border: 0px !important;
  681. border-radius: 12.5px !important;
  682. border-top-left-radius: 0 !important;
  683. border-bottom-left-radius: 0 !important;
  684. }
  685. }
  686. </style>