internetDetail.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. <template>
  2. <u-popup
  3. :show="visible"
  4. mode="bottom"
  5. :round="20"
  6. :closeable="true"
  7. :close-on-click-overlay="false"
  8. @close="closePopup"
  9. >
  10. <view class="internet-detail-container">
  11. <!-- 标题栏 -->
  12. <view class="popup-header">
  13. <text class="popup-title">设备详情</text>
  14. </view>
  15. <scroll-view scroll-y class="popup-body">
  16. <!-- 仪表盘区域 -->
  17. <view class="gauge-section">
  18. <view class="section-title">实时数据</view>
  19. <view class="gauge-grid">
  20. <view
  21. class="gauge-item"
  22. v-for="(item, index) in getGaugeData"
  23. :key="index"
  24. >
  25. <view
  26. class="gauge-card"
  27. v-for="(listItem, j) in item.list"
  28. :key="j"
  29. >
  30. <GaugeChart
  31. height="250px"
  32. :value="listItem.value"
  33. :min="listItem.min"
  34. :max="listItem.max"
  35. :title="listItem.name"
  36. :unit="listItem.unit"
  37. :colors="['#5470C6', '#91CC75', '#FAC858', '#EE6666']"
  38. :options="{ pointer: { itemStyle: { color: '#5470C6' } } }"
  39. :ref="'gaugeChartRef'"
  40. />
  41. </view>
  42. </view>
  43. </view>
  44. </view>
  45. <!-- 实时数据列表 -->
  46. <view class="data-section">
  47. <view class="section-title">运行状态</view>
  48. <view class="data-grid">
  49. <view class="data-item" v-for="(obj, key) in realData" :key="key">
  50. <view class="data-label">{{ obj.name }}:</view>
  51. <view
  52. class="data-value"
  53. v-if="
  54. obj.dataType &&
  55. (obj.dataType.type == 'enum' || obj.dataType.type == 'bool')
  56. "
  57. >
  58. {{ obj.specs[obj.value] }}
  59. </view>
  60. <view class="data-value" v-else>
  61. {{ obj.value }}
  62. <text v-if="obj.unit" class="data-unit">{{ obj.unit }}</text>
  63. </view>
  64. </view>
  65. </view>
  66. </view>
  67. <!-- 历史折线图 -->
  68. <view class="history-section">
  69. <view class="section-title">历史趋势</view>
  70. <view
  71. class="chart-card"
  72. v-for="(item, index) in historyData"
  73. :key="index"
  74. >
  75. <view class="chart-header">
  76. <text class="chart-name">{{ item.name }}</text>
  77. <view class="chart-stats">
  78. <text class="stat-item"
  79. >最高值<text class="stat-value"
  80. >{{ item.maxValue }} {{ item.unit }}</text
  81. ></text
  82. >
  83. <text class="stat-item"
  84. >最低值<text class="stat-value"
  85. >{{ item.minValue }} {{ item.unit }}</text
  86. ></text
  87. >
  88. <text class="stat-item"
  89. >平均值<text class="stat-value avg"
  90. >{{ item.avgValue }} {{ item.unit }}</text
  91. ></text
  92. >
  93. </view>
  94. </view>
  95. <view class="chart-tools">
  96. <dy-date-time-picker
  97. v-model="item.chartTime"
  98. :typeEnabled="{
  99. date: item.timeType == 'date',
  100. month: item.timeType == 'month',
  101. year: item.timeType == 'year',
  102. week: false,
  103. }"
  104. placeholder=""
  105. />
  106. <view class="time-type">
  107. <text
  108. :class="{ active: item.timeType === 'date' }"
  109. @click="dateClick(item, 'date')"
  110. >日</text
  111. >
  112. <text
  113. :class="{ active: item.timeType === 'month' }"
  114. @click="dateClick(item, 'month')"
  115. >月</text
  116. >
  117. <text
  118. :class="{ active: item.timeType === 'year' }"
  119. @click="dateClick(item, 'year')"
  120. >年</text
  121. >
  122. </view>
  123. <u-button
  124. type="primary"
  125. size="mini"
  126. @click="refreshSingleChart(item)"
  127. >查询</u-button
  128. >
  129. </view>
  130. <LineChart
  131. :data="item.trendData"
  132. title=""
  133. xAxisName="时间"
  134. :yAxisName="item.unit"
  135. height="300px"
  136. :options="{}"
  137. />
  138. </view>
  139. </view>
  140. </scroll-view>
  141. </view>
  142. </u-popup>
  143. </template>
  144. <script>
  145. import GaugeChart from "./GaugeChart.vue";
  146. import LineChart from "./LineChart.vue";
  147. import {
  148. getRealData,
  149. getHistoryData,
  150. getPhysicalModel,
  151. } from "@/api/ledgerAssets/equipment.js";
  152. import { getMonday } from "@/utils/utils.js";
  153. import { getAssetInfo } from "@/api/repair";
  154. import dayjs from "dayjs";
  155. export default {
  156. name: "InternetDetail",
  157. components: {
  158. GaugeChart,
  159. LineChart,
  160. },
  161. props: {},
  162. data() {
  163. return {
  164. visible: false,
  165. loading: false,
  166. realData: [],
  167. iotDashboardPoint: [],
  168. info: null,
  169. dict: {
  170. chartTime: {
  171. date: 3,
  172. month: 2,
  173. year: 1,
  174. },
  175. },
  176. gaugeData: [],
  177. historyData: [],
  178. id: null,
  179. };
  180. },
  181. computed: {
  182. getGaugeData() {
  183. let list = [];
  184. let gaugeData = this.iotDashboardPoint.length
  185. ? this.gaugeData.filter((item) =>
  186. this.iotDashboardPoint.find(
  187. (Point) => Point.identifier == item.identifier && Point.checked,
  188. ),
  189. )
  190. : this.gaugeData.filter((item, index) => index < 6);
  191. const perRow = 1;
  192. for (let i = 0; i < gaugeData.length; i += perRow) {
  193. list.push({
  194. list: gaugeData.slice(i, i + perRow),
  195. });
  196. }
  197. // this.$nextTick(() => {
  198. // console.log(this.$refs,'dadas')
  199. // // return
  200. // gaugeData.forEach((item, index) => {
  201. // if (this.$refs.gaugeChartRef[index]) {
  202. // this.$refs.gaugeChartRef[index].handleResize();
  203. // }
  204. // });
  205. // });
  206. return list;
  207. },
  208. },
  209. methods: {
  210. closePopup() {
  211. this.visible = false;
  212. },
  213. async open(id) {
  214. this.id = id;
  215. const { iotDashboardPoint } = await getAssetInfo(id);
  216. this.iotDashboardPoint = iotDashboardPoint;
  217. this.info = await getPhysicalModel(id);
  218. await this.getRealData();
  219. this.visible = true;
  220. },
  221. async getRealData() {
  222. this.loading = true;
  223. try {
  224. const res = await getRealData(this.id);
  225. this.realData = res;
  226. const tempData = this.info.properties || [];
  227. this.realData.forEach((item) => {
  228. tempData?.forEach((property) => {
  229. if (property.identifier == item.identifier) {
  230. item.max = property.dataType.specs.max;
  231. item.min = property.dataType.specs.min;
  232. item.propertyUnit = property.dataType.specs.unit;
  233. item.unitName = property.dataType.specs.unitName;
  234. item.specs = property.dataType.specs;
  235. item.dataType = property.dataType;
  236. item.propertyName = property.name;
  237. item.accessMode = property.accessMode;
  238. item.saveHistory = property.saveHistory;
  239. item.required = property.required;
  240. item.chartTime = dayjs(new Date()).format("YYYY-MM");
  241. // item.chartTime =dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss') ;
  242. item.timeType = "month";
  243. }
  244. });
  245. });
  246. const gaugeData = this.realData.filter(
  247. (item) => item.max !== undefined && item.min !== undefined,
  248. );
  249. this.gaugeData = gaugeData;
  250. const historyPromises = this.gaugeData.map((item) =>
  251. item.saveHistory ? this.getDefaultHistoryData(item) : null,
  252. );
  253. this.historyData = await Promise.all(historyPromises);
  254. } catch (error) {
  255. console.error("获取数据失败:", error);
  256. } finally {
  257. this.loading = false;
  258. }
  259. },
  260. async getDefaultHistoryData(item) {
  261. const chartTime = item.chartTime;
  262. const timeType = item.timeType || "month";
  263. let time = this.getTimeList(chartTime, timeType);
  264. // if (!time || !time.startTime || !time.endTime) {
  265. // const defaultDate = new Date().toISOString().split('T')[0];
  266. // time = {
  267. // startTime: defaultDate + ' 00:00:00',
  268. // endTime: defaultDate + ' 23:59:59'
  269. // };
  270. // }
  271. return {
  272. identifier: item.identifier || "",
  273. name: item.name || "",
  274. unit: item.unit || "",
  275. unitName: item.unitName || "",
  276. chartTime,
  277. timeType,
  278. trendData: {
  279. xAxis: [],
  280. series: [
  281. {
  282. name: "",
  283. data: [],
  284. color: "#5470C6",
  285. },
  286. ],
  287. },
  288. timeHistoryList: [],
  289. };
  290. },
  291. async getHistoryDatas(item) {
  292. try {
  293. const chartTime = item.chartTime;
  294. const timeType = item.timeType || "month";
  295. let time = this.getTimeList(chartTime, timeType);
  296. // if (!time || !time.startTime || !time.endTime) {
  297. // const defaultDate = new Date().toISOString().split('T')[0];
  298. // time = {
  299. // startTime: defaultDate + ' 00:00:00',
  300. // endTime: defaultDate + ' 23:59:59'
  301. // };
  302. // }
  303. // const apiTimeType = this.dict?.chartTime?.[timeType] || 'month';
  304. const res = await getHistoryData({
  305. startTime: time.startTime,
  306. endTime: time.endTime,
  307. property: item.identifier,
  308. substanceId: this.id,
  309. timeType: this.dict.chartTime[timeType],
  310. });
  311. const response = res || {};
  312. const trendData = {
  313. xAxis: [],
  314. series: [
  315. {
  316. name: response.name || item.name,
  317. data: [],
  318. color: "#5470C6",
  319. },
  320. ],
  321. };
  322. const timeHistoryList = Array.isArray(response.timeHistoryList)
  323. ? response.timeHistoryList
  324. : [];
  325. timeHistoryList.forEach((i) => {
  326. if (i && i.time !== undefined && i.value !== undefined) {
  327. trendData.xAxis.push(i.time);
  328. trendData.series[0].data.push(i.value);
  329. }
  330. });
  331. return {
  332. ...response,
  333. identifier: item.identifier || "",
  334. name: item.name || "",
  335. unit: item.unit || "",
  336. unitName: item.unitName || "",
  337. chartTime,
  338. timeType,
  339. trendData,
  340. timeHistoryList,
  341. };
  342. } catch (error) {
  343. console.error(
  344. `获取属性 ${item.name || item.identifier} 的历史数据失败:`,
  345. error,
  346. );
  347. return {
  348. identifier: item.identifier || "",
  349. name: item.name || "",
  350. unit: item.unit || "",
  351. unitName: item.unitName || "",
  352. chartTime: item.chartTime || new Date(),
  353. timeType: item.timeType || "month",
  354. trendData: {
  355. xAxis: [],
  356. series: [
  357. {
  358. name: item.name || "",
  359. data: [],
  360. color: "#5470C6",
  361. },
  362. ],
  363. },
  364. timeHistoryList: [],
  365. };
  366. }
  367. },
  368. async refreshSingleChart(item) {
  369. try {
  370. const updatedData = await this.getHistoryDatas(item);
  371. const index = this.historyData.findIndex(
  372. (d) => d.identifier === item.identifier,
  373. );
  374. if (index !== -1) {
  375. this.$set(this.historyData, index, updatedData);
  376. }
  377. } catch (error) {
  378. console.error(
  379. `刷新图表 ${item.name || item.identifier} 数据失败:`,
  380. error,
  381. );
  382. }
  383. },
  384. dateClick(item, dateType) {
  385. switch (dateType) {
  386. case "date":
  387. item.chartTime = dayjs(new Date()).format("YYYY-MM-DD");
  388. break;
  389. case "month":
  390. item.chartTime = dayjs(new Date()).format("YYYY-MM");
  391. break;
  392. case "year":
  393. item.chartTime = dayjs(new Date()).format("YYYY");
  394. break;
  395. default:
  396. break;
  397. }
  398. item.timeType = dateType;
  399. },
  400. getTimeList(data, timeType) {
  401. console.log(data, "date");
  402. // const fnNum = (num) => num < 10 ? '0' + num : num;
  403. let startTime, endTime;
  404. // const ndate = date ? new Date(date) : new Date();
  405. // const year = ndate.getFullYear();
  406. // const month = ndate.getMonth() + 1;
  407. // const day = ndate.getDate();
  408. let date = data.date || data;
  409. let year = data.year || data;
  410. let mon = getMonday(data.month || data);
  411. switch (timeType) {
  412. case "date":
  413. startTime = date + " 00:00:00";
  414. endTime = date + " 23:59:59";
  415. break;
  416. case "month":
  417. startTime = mon[0] + " 00:00:00";
  418. endTime = mon[1] + " 23:59:59";
  419. break;
  420. case "year":
  421. startTime = year + "-01-01 00:00:00";
  422. endTime = year + "-12-31 23:59:59";
  423. break;
  424. default:
  425. break;
  426. }
  427. return {
  428. startTime,
  429. endTime,
  430. };
  431. },
  432. },
  433. };
  434. </script>
  435. <style scoped lang="scss">
  436. .internet-detail-container {
  437. // display: flex;
  438. // flex-direction: column;
  439. height: calc(100vh - 100rpx);
  440. background: #f5f7fa;
  441. }
  442. .popup-header {
  443. display: flex;
  444. justify-content: center;
  445. align-items: center;
  446. padding: 30rpx 0;
  447. background: #fff;
  448. border-bottom: 1rpx solid #eee;
  449. .popup-title {
  450. font-size: 36rpx;
  451. font-weight: bold;
  452. color: #333;
  453. }
  454. }
  455. .popup-body {
  456. flex: 1;
  457. padding: 20rpx;
  458. height: 100%;
  459. }
  460. .section-title {
  461. font-size: 32rpx;
  462. font-weight: bold;
  463. color: #303133;
  464. margin-bottom: 20rpx;
  465. padding-left: 20rpx;
  466. border-left: 8rpx solid #1890ff;
  467. }
  468. .gauge-section {
  469. margin-bottom: 30rpx;
  470. }
  471. .gauge-grid {
  472. display: flex;
  473. flex-wrap: wrap;
  474. gap: 20rpx;
  475. }
  476. .gauge-item {
  477. display: flex;
  478. gap: 20rpx;
  479. width: 100%;
  480. }
  481. .gauge-card {
  482. flex: 1;
  483. min-width: 200rpx;
  484. background: #fff;
  485. border-radius: 20rpx;
  486. padding: 20rpx;
  487. box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
  488. }
  489. .data-section {
  490. margin-bottom: 30rpx;
  491. }
  492. .data-grid {
  493. display: grid;
  494. grid-template-columns: 1fr 1fr;
  495. gap: 20rpx;
  496. }
  497. .data-item {
  498. display: flex;
  499. align-items: center;
  500. background: #fff;
  501. padding: 24rpx;
  502. border-radius: 16rpx;
  503. box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
  504. }
  505. .data-label {
  506. font-size: 28rpx;
  507. color: #666;
  508. margin-right: 10rpx;
  509. }
  510. .data-value {
  511. font-size: 30rpx;
  512. color: #333;
  513. font-weight: bold;
  514. }
  515. .data-unit {
  516. font-size: 24rpx;
  517. color: #999;
  518. }
  519. .history-section {
  520. margin-bottom: 30rpx;
  521. }
  522. .chart-card {
  523. background: #fff;
  524. border-radius: 20rpx;
  525. padding: 24rpx;
  526. margin-bottom: 24rpx;
  527. box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
  528. }
  529. .chart-header {
  530. margin-bottom: 20rpx;
  531. }
  532. .chart-name {
  533. font-size: 30rpx;
  534. font-weight: bold;
  535. color: #333;
  536. display: block;
  537. margin-bottom: 16rpx;
  538. }
  539. .chart-stats {
  540. display: flex;
  541. flex-wrap: wrap;
  542. gap: 20rpx;
  543. }
  544. .stat-item {
  545. font-size: 26rpx;
  546. color: #666;
  547. }
  548. .stat-value {
  549. font-weight: bold;
  550. color: #ff3e52;
  551. margin-left: 8rpx;
  552. &.avg {
  553. color: #0840d5;
  554. }
  555. }
  556. .chart-tools {
  557. display: flex;
  558. align-items: center;
  559. flex-wrap: wrap;
  560. gap: 16rpx;
  561. margin-bottom: 20rpx;
  562. }
  563. .time-type {
  564. display: flex;
  565. gap: 16rpx;
  566. text {
  567. font-size: 28rpx;
  568. color: #666;
  569. padding: 8rpx 20rpx;
  570. border-radius: 8rpx;
  571. background: #f0f0f0;
  572. &.active {
  573. background: #1890ff;
  574. color: #fff;
  575. }
  576. }
  577. }
  578. </style>