lipenghui 3 miesięcy temu
rodzic
commit
d4c6d11381

+ 3 - 1
src/api/pms/device/index.ts

@@ -59,7 +59,9 @@ export const IotDeviceApi = {
   getIotDevice: async (id: number) => {
     return await request.get({ url: `/rq/iot-device/get?id=` + id })
   },
-
+  getIotDeviceTds: async (id: number) => {
+    return await request.get({ url: `/rq/iot-device/get/td?id=` + id })
+  },
   // 新增设备台账
   createIotDevice: async (data: IotDeviceVO) => {
     return await request.post({ url: `/rq/iot-device/create`, data })

+ 1 - 1
src/router/modules/remaining.ts

@@ -160,7 +160,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
           activeMenu: '/device/info'
         }
       },{
-        path: 'tddevice/detail/:id',
+        path: 'tddevice/detail/:id/:ifInline/:time/:name/:code',
         component: () => import('@/views/pms/device/monitor/TdDeviceInfo.vue'),
         name: 'TdDeviceDetail',
         meta: {

+ 1 - 0
src/views/pms/device/IotDeviceForm.vue

@@ -272,6 +272,7 @@
             <el-form-item :label="field.name" :prop="field.code" :rules="field.rules">
               <!-- 文本输入 -->
               <el-input
+
                 v-if="field.type === 'text'"
                 v-model="formData[field.code]"
                 :placeholder="'请输入' + field.name"

+ 269 - 4
src/views/pms/device/monitor/TdDeviceInfo.vue

@@ -1,14 +1,279 @@
 <template>
-  <ContentWrap>
-    <div>
-      资产编号:
-    </div>
+  <ContentWrap v-loading="formLoading">
+    <ContentWrap>
+      <el-form>
+        <el-row>
+          <el-col :span="6">
+            <el-form-item label="资产编码:" prop="deviceCode"
+              >{{ formData.deviceCode }}
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="设备类别:" prop="deviceName"
+              >{{ formData.deviceName }}
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="是否在线:" prop="ifInline"
+              >{{ getDictLabel(DICT_TYPE.IOT_DEVICE_STATUS, formData.ifInline) }}
+            </el-form-item>
+          </el-col>
+          <el-col :span="6">
+            <el-form-item label="最后在线时间:" prop="lastInlineTime"
+              >{{ formData.lastInlineTime }}
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </ContentWrap>
+    <ContentWrap>
+      <el-row>
+        <el-col :span="24">
+          <TdDeviceLabel :tags="specs" tag-width="24%" />
+        </el-col>
+      </el-row>
+    </ContentWrap>
+    <ContentWrap>
+      <div class="chart-container">
+        <!-- 日期选择区域 -->
+        <div class="date-controls">
+          <input
+            type="datetime-local"
+            v-model="startTime"
+            :max="endTime"
+            @change="handleDateChange"
+          />
+          <span class="separator">至</span>
+          <input
+            type="datetime-local"
+            v-model="endTime"
+            :min="startTime"
+            @change="handleDateChange"
+          />
+          <button class="query-btn" @click="fetchData">查询</button>
+        </div>
+
+        <!-- 图表容器 -->
+        <div v-loading="loading" class="chart" ref="chartRef"></div>
+      </div>
+    </ContentWrap>
   </ContentWrap>
 </template>
 
 <script setup lang="ts">
+import { DICT_TYPE, getDictLabel } from '@/utils/dict'
+import TdDeviceLabel from '@/views/pms/device/monitor/TdDeviceLabel.vue'
+import {IotDeviceApi} from "@/api/pms/device";
+
+const { params, name } = useRoute() // 查询参数
+const info = ref({})
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const id = params.id
+defineOptions({ name: 'TdDeviceDetail' })
+const formData = ref({
+  deviceCode: '',
+  deviceName: '',
+  ifInline: undefined,
+  lastInlineTime: ''
+})
+const specs = ref([])
+onMounted(async () => {
+  formLoading.value = true
+  formData.value.deviceCode = params.code
+  formData.value.deviceName = params.name
+  formData.value.lastInlineTime = params.time
+  formData.value.ifInline = params.ifInline
+  IotDeviceApi.getIotDeviceTds(id).then(res => {
+    specs.value = res
+    specs.value = specs.value.sort((a, b) => {
+      return b.modelOrder - a.modelOrder
+    })
+    formLoading.value = false
+  })
+  initDefaultDate()
+  initChart()
+  await fetchData()
+})
+
+
+
+import * as echarts from 'echarts'
+import dayjs from 'dayjs'
+
+// ECharts 实例
+const chartRef = ref(null)
+let chartInstance = null
+
+// 响应式数据
+const startTime = ref('')
+const endTime = ref('')
+const chartData = ref([])
+const loading = ref(false)
+
+// 初始化默认时间范围
+const initDefaultDate = () => {
+  const now = dayjs()
+  startTime.value = now.subtract(1, 'day').format('YYYY-MM-DDTHH:mm')
+  endTime.value = now.format('YYYY-MM-DDTHH:mm')
+}
+
+// 空数据占位处理
+const displayData = computed(() => {
+  return chartData.value.length > 0
+    ? chartData.value
+    : [{ time: startTime.value, value: 0 }, { time: endTime.value, value: 0 }]
+})
+
+// 初始化图表配置
+const initChart = () => {
+  chartInstance = echarts.init(chartRef.value)
+
+  const option = {
+    title: {
+      text: '数据趋势图',
+      left: 'center'
+    },
+    tooltip: {
+      trigger: 'axis',
+      formatter: params => {
+        if (chartData.value.length === 0) return '暂无数据'
+        return `${params[0].axisValue}<br/>${params[0].marker} ${params[0].value}`
+      }
+    },
+    xAxis: {
+      type: 'time',
+      boundaryGap: false,
+      axisLabel: {
+        formatter: value => dayjs(value).format('MM-DD HH:mm')
+      }
+    },
+    yAxis: {
+      type: 'value',
+      min: value => Math.max(0, value.min - 5)
+    },
+    series: [{
+      name: '数据',
+      type: 'line',
+      showSymbol: chartData.value.length > 0,
+      data: displayData.value.map(item => [item.time, item.value]),
+      areaStyle: chartData.value.length > 0 ? {} : null
+    }],
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    }
+  }
+
+  chartInstance.setOption(option)
+  window.addEventListener('resize', () => chartInstance.resize())
+}
 
+// 获取数据(示例)
+const fetchData = async () => {
+  try {
+    loading.value = true
+
+    // 模拟API请求
+    const mockData = [] // 测试空数据时设置为空数组
+
+    // 实际应替换为:
+    // const { data } = await api.getData({
+    //   start: dayjs(startTime.value).format('YYYY-MM-DD HH:mm:ss'),
+    //   end: dayjs(endTime.value).format('YYYY-MM-DD HH:mm:ss')
+    // })
+
+    chartData.value = mockData
+    updateChart()
+  } catch (error) {
+    console.error('数据获取失败:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+// 更新图表
+const updateChart = () => {
+  const options = {
+    series: [{
+      data: displayData.value.map(item => [item.time, item.value]),
+      showSymbol: chartData.value.length > 0,
+      areaStyle: chartData.value.length > 0 ? {} : null
+    }]
+  }
+  chartInstance.setOption(options)
+}
+
+// 日期变更处理
+const handleDateChange = () => {
+  if (dayjs(startTime.value).isAfter(endTime.value)) {
+    endTime.value = startTime.value
+  }
+}
+
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', () => chartInstance.resize())
+  chartInstance.dispose()
+})
 </script>
 <style scoped lang="scss">
+.container {
+  width: 100%;
+  margin: 20px auto;
+  padding: 24px;
+  //background: #f8f9fa;
+  border-radius: 12px;
+}
+.chart-container {
+  width: 100%;
+  height: 600px;
+  padding: 20px;
+  background: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0,0,0,0.1);
+}
+
+.date-controls {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin-bottom: 20px;
+}
+
+input[type="datetime-local"] {
+  padding: 8px 12px;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  transition: border-color 0.2s;
+}
+
+input[type="datetime-local"]:focus {
+  border-color: #409eff;
+  outline: none;
+}
+
+.separator {
+  color: #606266;
+}
+
+.query-btn {
+  padding: 8px 20px;
+  background: #409eff;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  transition: opacity 0.2s;
+}
+
+.query-btn:hover {
+  opacity: 0.8;
+}
 
+.chart {
+  width: 100%;
+  height: 500px;
+  margin-top: 20px;
+}
 </style>

+ 111 - 0
src/views/pms/device/monitor/TdDeviceLabel.vue

@@ -0,0 +1,111 @@
+<template>
+  <!-- TagGroup.vue -->
+  <div class="tag-group">
+    <div
+      v-for="(tag, index) in tags"
+      :key="index"
+      class="tag-item"
+      :class="{ 'active': selectedIndex === index }"
+      @click="selectTag(index)"
+    >
+      <span class="property">{{ tag.modelName }}</span>
+      <span class="value">{{ tag.valueType }}</span>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+
+const props = defineProps({
+  tags: {
+    type: Array,
+    required: true,
+    validator: (arr) => arr.every(item => item.label && item.value)
+  },
+  tagWidth: {
+    type: String,
+    default: '220px'
+  }
+})
+
+const selectedIndex = ref(-1)
+
+const selectTag = (index) => {
+  selectedIndex.value = selectedIndex.value === index ? -1 : index
+}
+</script>
+
+<style scoped>
+.tag-group {
+  display: flex;
+  flex-wrap: wrap;
+  row-gap: 10px;
+  column-gap: 16px;
+}
+
+.tag-item {
+  /* 基础样式 */
+  width: v-bind('props.tagWidth');
+  height: 48px;
+  background: #ffffff;
+  border: 1px solid #dcdfe6;
+  border-radius: 6px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  cursor: pointer;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+
+  /* 内容布局 */
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 16px;
+}
+
+/* 文字样式 */
+.property {
+  color: #606266;
+  font-size: 14px;
+  font-weight: 500;
+  flex-shrink: 0;
+}
+
+.value {
+  color: #909399;
+  font-size: 14px;
+  max-width: 60%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+/* 悬停效果 */
+.tag-item:hover {
+  transform: scale(1.03);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
+  z-index: 1;
+}
+
+/* 选中状态 */
+.tag-item.active {
+  background: #276cff;
+  border-color: #276cff;
+}
+
+.tag-item.active .property,
+.tag-item.active .value {
+  color: #fff !important;
+}
+
+/* 选中状态阴影 */
+.tag-item.active {
+  box-shadow: 0 4px 16px rgba(39, 108, 255, 0.2);
+}
+
+/* 禁用状态 */
+.tag-item.disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+  filter: grayscale(0.8);
+}
+</style>

+ 4 - 5
src/views/pms/device/monitor/index.vue

@@ -67,7 +67,7 @@
     </ContentWrap>
 
     <!-- 列表 -->
-    <ContentWrap>
+    <ContentWrap v-loading="loading">
       <template v-if="viewMode === 'card'">
         <el-row :gutter="16">
           <el-col v-for="item in list" :key="item.id" :xs="24" :sm="12" :md="12" :lg="6" class="mb-4">
@@ -148,7 +148,7 @@
                     class="flex-1 !px-2 !h-[32px] !ml-[10px] text-[13px]"
                     type="warning"
                     plain
-                    @click="openDetail(item.id)"
+                    @click="openDetail(item.id, item.ifInline, item.lastInlineTime,item.deviceName,item.deviceCode)"
                   >
                     <Icon icon="ep:view" class="mr-1" />
                     详情
@@ -293,7 +293,6 @@ const getList = async () => {
   loading.value = true
   try {
     const data = await IotDeviceApi.getIotDeviceTdPage(queryParams)
-    debugger
     list.value = data.list
     total.value = data.total
   } finally {
@@ -322,8 +321,8 @@ const resetQuery = () => {
 
 /** 打开详情 */
 const { push } = useRouter()
-const openDetail = (id: number) => {
-  push({ name: 'TdDeviceDetail', params: { id } })
+const openDetail = (id: number,ifInline: string, time:string, name:string,code:string) => {
+  push({ name: 'TdDeviceDetail', params: { id,ifInline, time,name,code } })
 }