TdDeviceInfo.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <template>
  2. <ContentWrap v-loading="formLoading">
  3. <ContentWrap>
  4. <el-form style="height:89px;margin-left: 20px;">
  5. <el-row style="display: flex;flex-direction: row; ">
  6. <el-col :span="8">
  7. <el-form-item prop="deviceCode">
  8. <template #label>
  9. <span class="custom-label">资产编码:</span>
  10. </template>
  11. <span class="custom-label">{{ formData.deviceCode }}</span>
  12. </el-form-item>
  13. </el-col>
  14. <el-col :span="8">
  15. <el-form-item prop="deviceName">
  16. <template #label>
  17. <span class="custom-label">设备类别:</span>
  18. </template>
  19. <span class="custom-label">{{ formData.deviceName }}</span>
  20. </el-form-item>
  21. </el-col>
  22. <el-col :span="8">
  23. <el-form-item prop="dept">
  24. <template #label>
  25. <span class="custom-label">所在部门:</span>
  26. </template>
  27. <span class="custom-label">{{ formData.dept }}</span>
  28. </el-form-item>
  29. </el-col>
  30. <el-col :span="8">
  31. <el-form-item prop="ifInline">
  32. <template #label>
  33. <span class="custom-label">是否在线:</span>
  34. </template>
  35. <template #default>
  36. <dict-tag :type="DICT_TYPE.IOT_DEVICE_STATUS" :value="formData.ifInline" />
  37. </template>
  38. </el-form-item>
  39. </el-col>
  40. <el-col :span="8">
  41. <el-form-item prop="lastInlineTime">
  42. <template #label>
  43. <span class="custom-label">最后数据时间:</span>
  44. </template>
  45. <span class="custom-label">{{ formData.lastInlineTime }}</span>
  46. </el-form-item>
  47. </el-col>
  48. <el-col :span="8">
  49. <el-form-item v-if="formData.vehicle" prop="vehicle">
  50. <template #label>
  51. <span class="custom-label">车牌号码:</span>
  52. </template>
  53. <span class="custom-label">{{ formData.vehicle }}</span>
  54. </el-form-item>
  55. </el-col>
  56. </el-row>
  57. </el-form>
  58. </ContentWrap>
  59. <ContentWrap>
  60. <el-row>
  61. <el-col :span="24">
  62. <TdDeviceLabel :tags="specs" @select="labelSelect" tag-width="24%" />
  63. </el-col>
  64. </el-row>
  65. </ContentWrap>
  66. <ContentWrap>
  67. <div class="chart-container">
  68. <!-- 图表容器 -->
  69. <el-date-picker
  70. v-model="dateRange"
  71. type="datetimerange"
  72. :default-time="[
  73. new Date(2000, 1, 1, 0, 0, 0),
  74. new Date(2000, 1, 1, 23, 59, 59)
  75. ]"
  76. start-placeholder="起始日期时间"
  77. end-placeholder="结束日期时间"
  78. format="YYYY-MM-DD HH:mm:ss"
  79. value-format="YYYY-MM-DD HH:mm:ss"
  80. @change="handleDateChange"
  81. />
  82. <div v-loading="loading" style="height: 100%" ref="chartContainer"></div>
  83. </div>
  84. </ContentWrap>
  85. </ContentWrap>
  86. </template>
  87. <script setup lang="ts">
  88. import { DICT_TYPE, getDictLabel } from '@/utils/dict'
  89. import TdDeviceLabel from '@/views/pms/device/monitor/TdDeviceLabel.vue'
  90. import {IotDeviceApi} from "@/api/pms/device";
  91. import * as echarts from 'echarts'
  92. import dayjs from 'dayjs'
  93. import {IotStatApi} from "@/api/pms/stat";
  94. const { params, name } = useRoute() // 查询参数
  95. const info = ref({})
  96. const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
  97. const id = params.id
  98. defineOptions({ name: 'TdDeviceDetail' })
  99. const formData = ref({
  100. deviceCode: '',
  101. deviceName: '',
  102. ifInline: undefined,
  103. lastInlineTime: '',
  104. dept:'',
  105. vehicle:''
  106. })
  107. const specs = ref([])
  108. // 响应式数据
  109. const startTime = ref('')
  110. const endTime = ref('')
  111. const topicName = ref([])
  112. const loading = ref(false)
  113. const topic = ref('')
  114. const handleDateChange = async (val) => {
  115. if (val && val.length === 2) {
  116. await getChart(val)
  117. await renderChart()
  118. }
  119. }
  120. const defaultEnd = dayjs()
  121. const defaultStart = defaultEnd.subtract(1, 'day')
  122. const dateRange = ref([
  123. defaultStart.format('YYYY-MM-DD HH:mm:ss'),
  124. defaultEnd.format('YYYY-MM-DD HH:mm:ss')
  125. ])
  126. const labelSelect =async (row) =>{
  127. topic.value = row.identifier
  128. topicName.value = row.modelName
  129. await getChart(dateRange.value)
  130. await renderChart()
  131. }
  132. const chartContainer = ref(null)
  133. let chartInstance = null
  134. // 时间格式化(HH:mm)
  135. const formatTime = timestamp => {
  136. return new Date(timestamp)
  137. .toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit',second:'2-digit' })
  138. .slice(0, 5)
  139. }
  140. const result = ref([])
  141. const getChart = async (range) =>{
  142. loading.value = true
  143. await IotStatApi.getDeviceInfoChart(params.code, topic.value, range[0], range[1]).then(res=>{
  144. result.value = res
  145. loading.value = false
  146. })
  147. }
  148. // 初始化图表
  149. const renderChart = async () => {
  150. if (!chartContainer.value) return
  151. // 销毁旧实例
  152. if (chartInstance) chartInstance.dispose()
  153. chartInstance = markRaw(echarts.init(chartContainer.value))
  154. const option = {
  155. title:{
  156. text: topicName.value+'数据趋势',
  157. left:'center',
  158. },
  159. tooltip: { trigger: 'axis', },
  160. xAxis: {
  161. type: 'category',
  162. data: result.value.map(d => dayjs(d.timestamp).format('YYYY-MM-DD HH:mm:ss')),
  163. // data: result.value.map(item => Object.keys(item)[0]),
  164. axisLabel: { rotate: 45 }, // X轴标签旋转防止重叠
  165. inverse: true,
  166. },
  167. yAxis: { type: 'value' },
  168. dataZoom: [{
  169. type: 'slider',
  170. xAxisIndex: 0,
  171. start: 0, // 初始显示范围开始位置
  172. end: 100 // 初始显示范围结束位置:ml-citation{ref="7" data="citationList"}
  173. }],
  174. series: [{
  175. data: result.value.map(d => d.value),
  176. // data: result.value.map(item => {
  177. // const key = Object.keys(item)[0]; // 获取当前元素的key
  178. // return item[key][0][topic.value]; // 提取value数组中第一个对象的属性
  179. // }),
  180. type: 'line',
  181. smooth: true,
  182. areaStyle: {} // 显示区域填充
  183. }]
  184. }
  185. chartInstance.setOption(option)
  186. // 窗口自适应
  187. window.addEventListener('resize', () => chartInstance.resize())
  188. }
  189. onMounted(async () => {
  190. formLoading.value = true
  191. formData.value.deviceCode = params.code
  192. formData.value.deviceName = params.name
  193. formData.value.lastInlineTime = params.time
  194. formData.value.ifInline = params.ifInline
  195. formData.value.dept = params.dept
  196. formData.value.vehicle = params.vehicle
  197. await IotDeviceApi.getIotDeviceTds(id).then(res => {
  198. specs.value = res
  199. specs.value = specs.value.sort((a, b) => {
  200. return b.modelOrder - a.modelOrder
  201. })
  202. formLoading.value = false
  203. topic.value = specs.value[0].identifier
  204. topicName.value = specs.value[0].modelName
  205. })
  206. await getChart(dateRange.value)
  207. await renderChart()
  208. })
  209. </script>
  210. <style scoped lang="scss">
  211. .container {
  212. width: 100%;
  213. margin: 20px auto;
  214. padding: 24px;
  215. //background: #f8f9fa;
  216. border-radius: 12px;
  217. }
  218. .chart-container {
  219. width: 100%;
  220. height: 600px;
  221. padding: 20px;
  222. background: #fff;
  223. border-radius: 8px;
  224. box-shadow: 0 2px 12px rgba(0,0,0,0.1);
  225. }
  226. .date-controls {
  227. display: flex;
  228. align-items: center;
  229. gap: 15px;
  230. margin-bottom: 20px;
  231. }
  232. input[type="datetime-local"] {
  233. padding: 8px 12px;
  234. border: 1px solid #dcdfe6;
  235. border-radius: 4px;
  236. transition: border-color 0.2s;
  237. }
  238. input[type="datetime-local"]:focus {
  239. border-color: #409eff;
  240. outline: none;
  241. }
  242. .separator {
  243. color: #606266;
  244. }
  245. .query-btn {
  246. padding: 8px 20px;
  247. background: #409eff;
  248. color: white;
  249. border: none;
  250. border-radius: 4px;
  251. cursor: pointer;
  252. transition: opacity 0.2s;
  253. }
  254. .query-btn:hover {
  255. opacity: 0.8;
  256. }
  257. //.chart {
  258. // width: 100%;
  259. // height: 500px;
  260. // margin-top: 20px;
  261. //}
  262. .custom-label{
  263. font-size: 17px;
  264. font-weight: bold;
  265. }
  266. </style>