malfunction.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <template>
  2. <div class="global-variable">
  3. <!-- tab 切换 -->
  4. <el-tabs v-model="activeName" @tab-click="handleClick">
  5. <el-tab-pane
  6. v-for="item in menuItems"
  7. :key="item.component"
  8. :label="item.name"
  9. :name="item.component"
  10. />
  11. </el-tabs>
  12. <!-- 查询条件 -->
  13. <div class="searchbox">
  14. <p>
  15. 单位:
  16. <selecttree
  17. size="small"
  18. style="width: 180px"
  19. placeholder="请选择所属公司"
  20. :list="tabConditions[activeTab].parentOpt"
  21. type="1"
  22. v-model="tabConditions[activeTab].companyCode"
  23. @change="parentChange"
  24. :defaultParentProps="{
  25. children: 'children',
  26. label: 'companyName',
  27. value: 'codeNumber',
  28. }"
  29. />
  30. </p>
  31. <p>
  32. 风机:
  33. <el-select
  34. size="small"
  35. style="width: 150px"
  36. v-model="tabConditions[activeTab].unitvalue"
  37. @change="getchedian"
  38. placeholder="请选择"
  39. >
  40. <el-option
  41. v-for="item in tabConditions[activeTab].unitoptions"
  42. :key="item.engineCode"
  43. :label="item.engineName"
  44. :value="item.engineCode"
  45. />
  46. </el-select>
  47. </p>
  48. <p>
  49. 测点:
  50. <el-select
  51. v-model="tabConditions[activeTab].monitoringvalue"
  52. size="small"
  53. clearable
  54. placeholder="请选择"
  55. :disabled="activeTab === 'Temperature'"
  56. >
  57. <el-option
  58. v-for="item in tabConditions[activeTab].monitoringoptions"
  59. :key="item.itemKey"
  60. :label="item.itemValue"
  61. :value="item.itemKey"
  62. />
  63. </el-select>
  64. </p>
  65. <p>
  66. 采样频率:
  67. <el-select
  68. v-model="tabConditions[activeTab].frequencyvalue"
  69. size="small"
  70. clearable
  71. placeholder="请选择"
  72. :disabled="activeTab === 'Temperature'"
  73. >
  74. <el-option
  75. v-for="item in tabConditions[activeTab].frequencyoptions"
  76. :key="item"
  77. :label="item"
  78. :value="item"
  79. />
  80. </el-select>
  81. </p>
  82. <p>
  83. 时间:
  84. <el-date-picker
  85. size="small"
  86. v-model="tabConditions[activeTab].timevalue"
  87. type="datetimerange"
  88. range-separator="至"
  89. start-placeholder="开始日期"
  90. end-placeholder="结束日期"
  91. />
  92. <!-- :picker-options="datePickerOptions" -->
  93. </p>
  94. <el-button
  95. type="primary"
  96. size="small"
  97. @click="onSearchClick"
  98. class="search-btn"
  99. >查询</el-button
  100. >
  101. </div>
  102. <!-- 动态组件内容 -->
  103. <div class="main-body">
  104. <keep-alive>
  105. <component
  106. ref="activeComponent"
  107. :is="activeTab"
  108. :codedata="tabData[activeTab].codedata"
  109. v-bind="
  110. activeTab === 'Temperature'
  111. ? { echartsdata: tabData.Temperature.echartsdata }
  112. : {}
  113. "
  114. :totalCount="tabData[activeTab].totalCount"
  115. :totalPage="tabData[activeTab].totalPage"
  116. :fieldCode="tabConditions[activeTab].companyCode"
  117. :windTurbineNumber="tabConditions[activeTab].unitvalue"
  118. @updatePage="handlePageChange"
  119. />
  120. </keep-alive>
  121. </div>
  122. </div>
  123. </template>
  124. <script>
  125. import * as FileSaver from "file-saver";
  126. import * as XLSX from "xlsx";
  127. import {
  128. getSysOrganizationAuthTreeByRoleId,
  129. windEngineGrouPage,
  130. queryDetectionDic,
  131. } from "@/api/ledger.js";
  132. import selecttree from "../../components/selecttree.vue";
  133. import Bearing from "./components/malfunction/bearing.vue";
  134. import Dissymmetry from "./components/malfunction/dissymmetry.vue";
  135. import Gear from "./components/malfunction/gear.vue";
  136. import Loose from "./components/malfunction/loose.vue";
  137. import Misalignment from "./components/malfunction/misalignment.vue";
  138. import Temperature from "./components/malfunction/temperature.vue";
  139. import axios from "axios";
  140. export default {
  141. components: {
  142. selecttree,
  143. Bearing,
  144. Dissymmetry,
  145. Gear,
  146. Loose,
  147. Misalignment,
  148. Temperature,
  149. },
  150. data() {
  151. return {
  152. activeTab: "Bearing",
  153. activeName: "Bearing",
  154. menuItems: [
  155. {
  156. name: "轴承诊断",
  157. icon: require("@/assets/img/ZC.png"),
  158. component: "Bearing",
  159. },
  160. {
  161. name: "齿轮诊断",
  162. icon: require("@/assets/img/SZ.png"),
  163. component: "Gear",
  164. },
  165. {
  166. name: "不对中诊断",
  167. icon: require("@/assets/img/DZ.png"),
  168. component: "Dissymmetry",
  169. },
  170. {
  171. name: "不平衡诊断",
  172. icon: require("@/assets/img/DC.png"),
  173. component: "Misalignment",
  174. },
  175. {
  176. name: "松动诊断",
  177. icon: require("@/assets/img/SD.png"),
  178. component: "Loose",
  179. },
  180. {
  181. name: "温度诊断",
  182. icon: require("@/assets/img/WD.png"),
  183. component: "Temperature",
  184. },
  185. ],
  186. tabConditions: {
  187. Bearing: {},
  188. Gear: {},
  189. Dissymmetry: {},
  190. Misalignment: {},
  191. Loose: {},
  192. Temperature: {},
  193. },
  194. tabData: {
  195. Bearing: { codedata: [], totalCount: 0, totalPage: 0 },
  196. Gear: { codedata: [], totalCount: 0, totalPage: 0 },
  197. Dissymmetry: { codedata: [], totalCount: 0, totalPage: 0 },
  198. Misalignment: { codedata: [], totalCount: 0, totalPage: 0 },
  199. Loose: { codedata: [], totalCount: 0, totalPage: 0 },
  200. Temperature: {
  201. codedata: [],
  202. totalCount: 0,
  203. totalPage: 0,
  204. echartsdata: {},
  205. },
  206. },
  207. datePickerOptions: {
  208. onPick: ({ minDate, maxDate }) => {
  209. if (minDate && !maxDate) {
  210. const maxTime = new Date(
  211. minDate.getTime() + 30 * 24 * 60 * 60 * 1000
  212. );
  213. this.datePickerOptions.disabledDate = (time) => {
  214. return (
  215. time.getTime() < minDate.getTime() ||
  216. time.getTime() > maxTime.getTime()
  217. );
  218. };
  219. } else {
  220. this.datePickerOptions.disabledDate = () => false;
  221. }
  222. },
  223. disabledDate: () => false,
  224. },
  225. };
  226. },
  227. created() {
  228. for (const key in this.tabConditions) {
  229. this.tabConditions[key] = this.defaultCondition();
  230. }
  231. this.GETtree();
  232. },
  233. methods: {
  234. defaultCondition() {
  235. return {
  236. unitvalue: "",
  237. unitoptions: [],
  238. companyCode: "",
  239. parentOpt: [],
  240. timevalue: [],
  241. monitoringvalue: "",
  242. monitoringoptions: [],
  243. frequencyvalue: "",
  244. frequencyoptions: [],
  245. };
  246. },
  247. handleClick(tab) {
  248. this.activeTab = tab.name;
  249. },
  250. async GETtree() {
  251. const res = await getSysOrganizationAuthTreeByRoleId();
  252. const treedata = res.data;
  253. const processed = this.processTreeData(treedata);
  254. for (const key in this.tabConditions) {
  255. this.tabConditions[key].parentOpt = processed;
  256. }
  257. },
  258. parentChange(data) {
  259. const condition = this.tabConditions[this.activeTab];
  260. if (!data?.codeNumber) return;
  261. condition.unitvalue = "";
  262. condition.monitoringvalue = "";
  263. condition.frequencyvalue = "";
  264. windEngineGrouPage({
  265. fieldCode: data.codeNumber,
  266. pageNum: 1,
  267. pageSize: 99,
  268. }).then((res) => {
  269. condition.unitoptions = res.data.list;
  270. });
  271. axios
  272. .get(`/ETLapi/waveData/getAllSamplingFrequency/${data.codeNumber}`)
  273. .then((res) => {
  274. condition.frequencyoptions = (res.data.datas || [])
  275. .map(item => Number(item))
  276. .filter(num => num >= 12800)
  277. .map(num => num.toString());
  278. });
  279. condition.companyCode = data.codeNumber;
  280. },
  281. getchedian(value) {
  282. const condition = this.tabConditions[this.activeTab];
  283. const companyCode = condition.companyCode;
  284. axios
  285. .post(`/ETLapi/waveData/getAllMesurePointName/${companyCode}/${value}`)
  286. .then((res) => {
  287. condition.monitoringvalue = "";
  288. if (res.data.code === 500) {
  289. condition.monitoringoptions = [];
  290. this.$message({
  291. message: "当前风场不存在测点和采样频率数据" || "未知错误",
  292. type: "warning",
  293. });
  294. return;
  295. }
  296. if (res.data.code === 200) {
  297. const datas = Array.isArray(res.data.datas) ? res.data.datas : [];
  298. condition.monitoringoptions = datas;
  299. if (datas.length === 0) {
  300. this.$message({ message: "暂无数据", type: "warning" });
  301. }
  302. }
  303. })
  304. .catch((err) => {
  305. console.error("测点请求失败:", err);
  306. condition.monitoringoptions = [];
  307. });
  308. },
  309. processTreeData(treeData) {
  310. const processedData = [];
  311. function processNode(node) {
  312. if (node.codeType === "field") {
  313. node.companyName = node.fieldName;
  314. }
  315. if (node.children && node.children.length > 0) {
  316. node.children.forEach(processNode);
  317. }
  318. }
  319. treeData.forEach((root) => {
  320. processNode(root);
  321. processedData.push(root);
  322. });
  323. return processedData;
  324. },
  325. onSearchClick() {
  326. this.conditions(1, false); // 主动查询,第一页,非分页触发
  327. },
  328. handlePageChange(page) {
  329. this.conditions(page, true); // true 表示分页触发
  330. },
  331. conditions(page, isPageChange = false) {
  332. const tab = this.activeTab;
  333. this.$nextTick(() => {
  334. const comp = this.$refs.activeComponent;
  335. if (comp && typeof comp.reset === "function") {
  336. comp.reset();
  337. }
  338. });
  339. // 把后面接口请求部分放进延迟里,确保 reset 先执行完成
  340. setTimeout(() => {
  341. const condition = this.tabConditions[tab];
  342. if (tab === "Temperature") {
  343. const temperature = {
  344. windCode: condition.companyCode,
  345. windTurbineNumberList: [condition.unitvalue],
  346. startTime: this.$formatDateTWO(condition.timevalue[0]),
  347. endTime: this.$formatDateTWO(condition.timevalue[1]),
  348. pageNo: page,
  349. pageSize: 500,
  350. };
  351. const loading = this.$loading({
  352. lock: true,
  353. text: "温度诊断数据加载中,请稍候…",
  354. spinner: "el-icon-loading",
  355. background: "rgba(0, 0, 0, 0.8)",
  356. });
  357. const emptyEchartsData = {
  358. gearbox_oil: { timestamps: [], values: [] },
  359. generator_drive_end: { timestamps: [], values: [] },
  360. generator_nondrive_end: { timestamps: [], values: [] },
  361. main_bearing: { timestamps: [], values: [] },
  362. };
  363. const trendRequest = () => {
  364. const trendStart = performance.now();
  365. return axios
  366. .post("/AnalysisMulti/SPRT/trend", temperature)
  367. .then((res) => {
  368. const trendEnd = performance.now();
  369. console.log(
  370. `温度诊断 trend 接口耗时: ${(
  371. (trendEnd - trendStart) /
  372. 1000
  373. ).toFixed(2)}s`
  374. );
  375. const echartsdata = res.data.data || {};
  376. this.tabData[tab].echartsdata = echartsdata;
  377. })
  378. .catch((err) => {
  379. console.error("趋势图请求失败:", err);
  380. this.$message.error("温度趋势图数据请求失败,请稍后再试");
  381. });
  382. };
  383. const thresholdStart = performance.now();
  384. const thresholdReq = axios
  385. .post("/AnalysisMulti/temperature/threshold", temperature)
  386. .then((res) => {
  387. const thresholdEnd = performance.now();
  388. console.log(
  389. `温度诊断 threshold 接口耗时: ${(
  390. (thresholdEnd - thresholdStart) /
  391. 1000
  392. ).toFixed(2)}s`
  393. );
  394. const data = res.data.data.records || [];
  395. this.tabData[tab].codedata = data;
  396. this.tabData[tab].totalCount = res.data.data.totalSize || 0;
  397. if (!data.length) {
  398. this.$message.warning("暂无诊断数据");
  399. this.tabData[tab].echartsdata = emptyEchartsData;
  400. return Promise.resolve("skip trend");
  401. }
  402. return isPageChange ? Promise.resolve() : trendRequest();
  403. })
  404. .catch((err) => {
  405. console.error("温度诊断请求失败:", err);
  406. this.$message.error("温度诊断数据请求失败,请稍后再试");
  407. });
  408. Promise.resolve(thresholdReq).finally(() => {
  409. loading.close();
  410. });
  411. } else {
  412. const params = {
  413. samplingFrequency: condition.frequencyvalue,
  414. windCode: condition.companyCode,
  415. windTurbineNumberList: [condition.unitvalue],
  416. mesureNameList: [condition.monitoringvalue],
  417. startTime: this.$formatDateTWO(condition.timevalue[0]),
  418. endTime: this.$formatDateTWO(condition.timevalue[1]),
  419. pageNo: page,
  420. pageSize: 10,
  421. };
  422. const startTime = performance.now();
  423. axios
  424. .post("/ETLapi/waveData/getMesureDataWithSF", params)
  425. .then((res) => {
  426. const endTime = performance.now();
  427. console.log(
  428. `其他 tab 接口请求耗时: ${(endTime - startTime).toFixed(2)}ms`
  429. );
  430. const data = res.data || {};
  431. this.tabData[tab].codedata = data.datas || [];
  432. this.tabData[tab].totalCount = data.totalCount || 0;
  433. this.tabData[tab].totalPage = data.totalPage || 0;
  434. });
  435. }
  436. }, 300); // 延迟100毫秒执行接口请求逻辑
  437. },
  438. },
  439. };
  440. </script>
  441. <style lang="scss" scoped>
  442. .global-variable {
  443. padding: 10px;
  444. }
  445. .head {
  446. padding: 5px 0;
  447. display: flex;
  448. justify-content: space-between;
  449. ul {
  450. display: flex;
  451. width: 500px;
  452. height: 65px;
  453. justify-content: space-between;
  454. li {
  455. cursor: pointer;
  456. padding: 8px 12px 0 12px;
  457. border-radius: 4px;
  458. transition: all 0.3s;
  459. &:hover {
  460. background-color: #f0f0f0;
  461. }
  462. &.active {
  463. background-color: #e6f7ff;
  464. border-bottom: 2px solid var(--header-bg);
  465. span {
  466. color: var(--header-bg);
  467. font-weight: bold;
  468. }
  469. }
  470. .Simg {
  471. width: 30px;
  472. height: 30px;
  473. display: flex;
  474. align-items: center;
  475. justify-content: center;
  476. margin: 0 auto;
  477. img {
  478. max-width: 100%;
  479. max-height: 100%;
  480. }
  481. }
  482. span {
  483. display: block;
  484. font-size: 12px;
  485. font-weight: 600;
  486. text-align: center;
  487. margin-top: 5px;
  488. }
  489. }
  490. }
  491. }
  492. .searchbox {
  493. display: flex;
  494. margin-bottom: 10px;
  495. align-items: center;
  496. flex-wrap: wrap;
  497. // padding-top: 10px;
  498. p {
  499. margin-right: 10px;
  500. margin-bottom: 0;
  501. margin-top: 10px;
  502. display: flex;
  503. align-items: center;
  504. .el-select,
  505. .el-date-picker {
  506. margin-left: 10px;
  507. }
  508. }
  509. .el-select {
  510. width: 180px;
  511. }
  512. .search-btn {
  513. margin-top: 10px;
  514. }
  515. }
  516. .main-body {
  517. margin-top: 10px;
  518. // border: 1px solid #ebeef5;
  519. border-radius: 4px;
  520. padding: 10px;
  521. background: #fff;
  522. }
  523. .el-range-editor.el-input__inner {
  524. width: 370px;
  525. }
  526. /* 放在你的组件 <style scoped> 或全局样式里 */
  527. .custom-loading-style .el-loading-spinner i {
  528. font-size: 26px;
  529. color: #409eff; /* Element UI 主色调,更柔和 */
  530. }
  531. .custom-loading-style .el-loading-text {
  532. font-size: 14px;
  533. color: #303133;
  534. letter-spacing: 0.5px;
  535. }
  536. </style>