فهرست منبع

Merge branch 'feature/monitoring-query'

Zimo 10 ساعت پیش
والد
کامیت
c38d6e8557
2فایلهای تغییر یافته به همراه218 افزوده شده و 114 حذف شده
  1. 8 0
      src/api/pms/device/index.ts
  2. 210 114
      src/views/oli-connection/monitoring-query/index.vue

+ 8 - 0
src/api/pms/device/index.ts

@@ -180,6 +180,14 @@ export const IotDeviceApi = {
     return await request.get({ url: `/rq/iot-device/td/ly/page`, params })
   },
 
+  getMonitoringQuery: async (params: any) => {
+    return await request.get({ url: `rq/stat/td/ly/chart`, params })
+  },
+
+  exportMonitoringQuery: async (params: any) => {
+    return await request.download({ url: `rq/ly/export`, params })
+  },
+
   // 新增时根据部门id获取设备列表
   getIotDeviceSetOptions: async (id: any) => {
     return await request.get({ url: `/rq/iot-device/dept/${id}` })

+ 210 - 114
src/views/oli-connection/monitoring-query/index.vue

@@ -1,107 +1,176 @@
 <script lang="ts" setup>
 import { IotDeviceApi } from '@/api/pms/device'
-import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 import { useUserStore } from '@/store/modules/user'
-import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import download from '@/utils/download'
 import { rangeShortcuts } from '@/utils/formatTime'
 import { useDebounceFn } from '@vueuse/core'
+import { dayjs, ElMessage } from 'element-plus'
+import { computed, ref } from 'vue'
+import { useI18n } from 'vue-i18n'
 
 const { t } = useI18n()
 
 const id = useUserStore().getUser.deptId ?? 157
-
 const deptId = id
 
 interface Query {
   deptId?: number
   deviceName?: string
   deviceCode?: string
-  ifInline?: number
-  pageNo: number
-  pageSize: number
-  time: string[]
+  time?: string[]
 }
 
 const query = ref<Query>({
-  pageNo: 1,
-  pageSize: 12,
-  deptId: id,
+  deviceName: '',
+  deviceCode: '',
   time: []
 })
 
-interface OliDevice {
-  id: number
-  carId?: number
+const pageSize = ref(100)
+const historyStack = ref<string[]>([])
+
+const hasSearched = ref(false)
+
+// 数据列表类型
+interface ListItem {
   deviceName: string
-  deviceCode: string
-  assetClassName: string
-  deviceStatus: string
-  ifInline: number
-  lastInlineTime: string
-  carOnline: string
-  deptName: string
-  vehicleName: string
+  serialNumber: string
+  identity: string
+  value: string
+  ts: string
 }
 
-const list = ref<OliDevice[]>([])
-const total = ref(0)
-
+const list = ref<ListItem[]>([])
 const loading = ref(false)
 
+const isConditionValid = computed(() => {
+  const hasTime = query.value.time && query.value.time.length === 2
+  const hasIdentity = !!query.value.deviceName || !!query.value.deviceCode
+  return hasTime && hasIdentity
+})
+
+const canGoBack = computed(() => historyStack.value.length > 0)
+const canGoNext = computed(() => list.value.length >= pageSize.value)
+
 const loadList = useDebounceFn(async function () {
+  if (!isConditionValid.value) {
+    // list.value = []
+    return
+  }
+
   loading.value = true
   try {
-    const data = await IotDeviceApi.getIotDeviceOliConnectPage(query.value)
-    // const data = await IotDeviceApi.getIotDeviceTdPage(query.value)
-    list.value = data.list
-    total.value = data.total
+    const { time, ...other } = query.value
+
+    const params = {
+      ...other,
+      // 直接使用 query 中的时间
+      beginTime: time?.[0],
+      endTime: time?.[1],
+      pageSize: pageSize.value
+    }
+
+    const data = await IotDeviceApi.getMonitoringQuery(params)
+    list.value = data.data || []
+  } catch (error) {
+    console.error(error)
+    list.value = []
   } finally {
     loading.value = false
   }
 })
 
-function handleSizeChange(val: number) {
-  query.value.pageSize = val
-  handleQuery()
-}
-
-function handleCurrentChange(val: number) {
-  query.value.pageNo = val
-  loadList()
-}
+function handleQuery() {
+  if (!query.value.deviceName && !query.value.deviceCode) {
+    ElMessage.warning('请输入设备名称或设备编码')
+    return
+  }
 
-function handleQuery(setPage = true) {
-  if (setPage) {
-    query.value.pageNo = 1
+  if (!query.value.time || query.value.time.length !== 2) {
+    ElMessage.warning('请选择时间范围')
+    return
   }
+
+  hasSearched.value = true
+
+  historyStack.value = []
+
   loadList()
 }
 
-function resetQuery() {
+function handleReset() {
   query.value = {
-    pageNo: 1,
-    pageSize: 12,
-    deptId: id,
+    deviceCode: '',
+    deviceName: '',
     time: []
   }
+  list.value = []
+  historyStack.value = []
+  hasSearched.value = false
+}
+
+function handleNext() {
+  if (list.value.length === 0) return
+  if (!query.value.time || query.value.time.length < 2) return
+
+  const lastItem = list.value[list.value.length - 1]
+
+  if (lastItem && lastItem.ts) {
+    historyStack.value.push(query.value.time[1])
+    query.value.time = [query.value.time[0], dayjs(lastItem.ts).format('YYYY-MM-DD HH:mm:ss')]
+    loadList()
+  } else {
+    ElMessage.warning('无法获取最后一条数据的时间,无法跳转')
+  }
+}
+
+function handlePrev() {
+  if (historyStack.value.length === 0) return
+  if (!query.value.time || query.value.time.length < 2) return
 
-  handleQuery()
+  const prevEndTime = historyStack.value.pop()
+
+  if (prevEndTime) {
+    query.value.time = [query.value.time[0], dayjs(prevEndTime).format('YYYY-MM-DD HH:mm:ss')]
+    loadList()
+  }
 }
 
-watch(
-  [
-    () => query.value.deptId,
-    () => query.value.deviceName,
-    () => query.value.deviceCode,
-    () => query.value.ifInline
-  ],
-  () => {
-    handleQuery()
-  },
-  { immediate: true }
-)
-
-const { ZmTable, ZmTableColumn } = useTableComponents<OliDevice>()
+function handleSizeChange() {
+  if (isConditionValid.value) {
+    historyStack.value = []
+    loadList()
+  }
+}
+
+const exportLoading = ref(false)
+
+const handleExport = useDebounceFn(async function () {
+  if (!query.value.deviceName && !query.value.deviceCode) {
+    ElMessage.warning('请输入设备名称或设备编码')
+    return
+  }
+
+  if (!query.value.time || query.value.time.length !== 2) {
+    ElMessage.warning('请选择时间范围')
+    return
+  }
+  exportLoading.value = true
+  try {
+    const { time, ...other } = query.value
+
+    const params = {
+      ...other,
+      beginTime: time?.[0],
+      endTime: time?.[1]
+    }
+
+    const data = await IotDeviceApi.exportMonitoringQuery(params)
+    download.excel(data, '监控查询.xls')
+  } finally {
+    exportLoading.value = false
+  }
+}, 300)
 </script>
 
 <template>
@@ -109,14 +178,9 @@ const { ZmTable, ZmTableColumn } = useTableComponents<OliDevice>()
     class="grid grid-cols-[15%_1fr] grid-rows-[62px_1fr] gap-4 h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
   >
     <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2">
-      <DeptTreeSelect
-        :top-id="156"
-        :deptId="deptId"
-        v-model="query.deptId"
-        :init-select="false"
-        :show-title="false"
-      />
+      <DeptTreeSelect :top-id="156" :deptId="deptId" :init-select="false" :show-title="false" />
     </div>
+
     <el-form
       size="default"
       class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
@@ -140,42 +204,33 @@ const { ZmTable, ZmTableColumn } = useTableComponents<OliDevice>()
             class="!w-240px"
           />
         </el-form-item>
-
-        <el-form-item :label="t('monitor.ifInline')">
-          <el-select
-            v-model="query.ifInline"
-            :placeholder="t('monitor.ifInlineHolder')"
-            clearable
-            class="!w-240px"
-          >
-            <el-option
-              v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_STATUS)"
-              :key="dict.value"
-              :label="dict.label"
-              :value="dict.value"
-            />
-          </el-select>
-        </el-form-item>
         <el-form-item label="时间">
           <el-date-picker
             v-model="query.time"
             value-format="YYYY-MM-DD HH:mm:ss"
-            type="daterange"
+            type="datetimerange"
             start-placeholder="开始日期"
             end-placeholder="结束日期"
             :shortcuts="rangeShortcuts"
             :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-            class="!w-220px"
+            class="!w-352px"
           />
         </el-form-item>
         <el-form-item>
           <el-button type="primary" @click="handleQuery()">
             <Icon icon="ep:search" class="mr-5px" /> 搜索
           </el-button>
-          <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
+          <el-button @click="handleReset()">
+            <Icon icon="ep:refresh" class="mr-1" /> 重置
+          </el-button>
+          <el-button :loading="exportLoading" plain type="success" @click="handleExport">
+            <Icon class="mr-5px" icon="ep:download" />
+            导出
+          </el-button>
         </el-form-item>
       </div>
     </el-form>
+
     <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col p-4">
       <div class="flex-1 relative">
         <el-auto-resizer class="absolute">
@@ -184,41 +239,82 @@ const { ZmTable, ZmTableColumn } = useTableComponents<OliDevice>()
               :data="list"
               :loading="loading"
               :width="width"
-              :max-height="height"
               :height="height"
+              :max-height="height"
             >
-              <zm-table-column type="index" :label="t('monitor.serial')" :width="60" />
-              <zm-table-column prop="deviceName" :label="t('monitor.deviceName')" />
-              <zm-table-column prop="deviceCode" :label="t('monitor.deviceCode')" />
-              <zm-table-column prop="assetClassName" :label="t('monitor.category')" />
-              <zm-table-column prop="deviceStatus" :label="t('monitor.status')">
-                <template #default="scope">
-                  <dict-tag :type="DICT_TYPE.PMS_DEVICE_STATUS" :value="scope.row.deviceStatus" />
-                </template>
-              </zm-table-column>
-              <zm-table-column prop="ifInline" :label="t('monitor.ifInline')">
-                <template #default="scope">
-                  <dict-tag :type="DICT_TYPE.IOT_DEVICE_STATUS" :value="scope.row.ifInline" />
-                </template>
-              </zm-table-column>
-              <zm-table-column prop="lastInlineTime" :label="t('monitor.latestDataTime')" />
+              <template #empty>
+                <el-empty
+                  v-if="!hasSearched"
+                  description="请至少输入【设备名称】或【设备编码】,并选择【时间范围】进行搜索"
+                />
+                <el-empty v-else description="暂无数据" />
+              </template>
+
+              <zm-table-column type="index" label="序号" :width="60" align="center" />
+              <zm-table-column
+                prop="deviceName"
+                title="设备名称"
+                label="设备名称"
+                align="center"
+                min-width="120"
+              />
+              <zm-table-column
+                prop="serialNumber"
+                title="设备编码"
+                label="设备编码"
+                align="center"
+                min-width="120"
+              />
+              <zm-table-column
+                prop="identity"
+                title="属性"
+                label="属性"
+                align="center"
+                min-width="100"
+              />
+              <zm-table-column
+                prop="value"
+                title="值"
+                label="值"
+                align="center"
+                min-width="100"
+                cover-formatter
+                :real-value="(row: ListItem) => row.value"
+              />
+              <zm-table-column
+                prop="ts"
+                title="时间"
+                :formatter="(row: ListItem) => dayjs(row.ts).format('YYYY-MM-DD HH:mm:ss')"
+                label="时间"
+                align="center"
+                min-width="160"
+              />
             </zm-table>
           </template>
         </el-auto-resizer>
       </div>
-      <div class="h-10 mt-4 flex items-center justify-end">
-        <el-pagination
-          size="default"
-          v-show="total > 0"
-          v-model:current-page="query.pageNo"
-          v-model:page-size="query.pageSize"
-          :background="true"
-          :page-sizes="[12, 20, 30, 50, 100]"
-          :total="total"
-          layout="total, sizes, prev, pager, next, jumper"
-          @size-change="handleSizeChange"
-          @current-change="handleCurrentChange"
-        />
+
+      <div
+        class="h-[50px] flex items-center justify-end border-t border-gray-100 dark:border-gray-700 mt-2 gap-4"
+      >
+        <div class="text-sm text-gray-500">
+          每页显示:
+          <el-select v-model="pageSize" class="!w-[80px]" size="default" @change="handleSizeChange">
+            <el-option :value="10" label="10" />
+            <el-option :value="20" label="20" />
+            <el-option :value="50" label="50" />
+            <el-option :value="100" label="100" />
+          </el-select>
+        </div>
+
+        <div class="flex gap-2">
+          <el-button size="default" :disabled="!canGoBack || loading" @click="handlePrev">
+            <Icon icon="ep:arrow-left" /> 上一页
+          </el-button>
+          <el-button size="default" :disabled="!canGoNext || loading" @click="handleNext">
+            下一页 <Icon icon="ep:arrow-right" />
+          </el-button>
+        </div>
       </div>
     </div>
   </div>