|
@@ -1,49 +1,19 @@
|
|
|
<script lang="ts" setup>
|
|
<script lang="ts" setup>
|
|
|
import { IotDeviceApi } from '@/api/pms/device'
|
|
import { IotDeviceApi } from '@/api/pms/device'
|
|
|
|
|
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
|
|
|
import { useUserStore } from '@/store/modules/user'
|
|
import { useUserStore } from '@/store/modules/user'
|
|
|
import { rangeShortcuts } from '@/utils/formatTime'
|
|
import { rangeShortcuts } from '@/utils/formatTime'
|
|
|
import { useDebounceFn } from '@vueuse/core'
|
|
import { useDebounceFn } from '@vueuse/core'
|
|
|
-import { Column, dayjs } from 'element-plus'
|
|
|
|
|
-
|
|
|
|
|
-const columns = ref<Column[]>([
|
|
|
|
|
- {
|
|
|
|
|
- align: 'center',
|
|
|
|
|
- dataKey: 'deviceName',
|
|
|
|
|
- title: '设备名称',
|
|
|
|
|
- width: 100
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- align: 'center',
|
|
|
|
|
- dataKey: 'serialNumber',
|
|
|
|
|
- title: '设备编码',
|
|
|
|
|
- width: 100
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- align: 'center',
|
|
|
|
|
- dataKey: 'identity',
|
|
|
|
|
- title: '属性',
|
|
|
|
|
- width: 100
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- align: 'center',
|
|
|
|
|
- dataKey: 'value',
|
|
|
|
|
- title: '值',
|
|
|
|
|
- width: 100
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- align: 'center',
|
|
|
|
|
- dataKey: 'ts',
|
|
|
|
|
- title: '时间',
|
|
|
|
|
- width: 100
|
|
|
|
|
- }
|
|
|
|
|
-])
|
|
|
|
|
|
|
+import { dayjs, ElMessage } from 'element-plus'
|
|
|
|
|
+import { computed, ref } from 'vue'
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
const { t } = useI18n()
|
|
|
|
|
|
|
|
|
|
+// --- 状态定义 ---
|
|
|
const id = useUserStore().getUser.deptId ?? 157
|
|
const id = useUserStore().getUser.deptId ?? 157
|
|
|
-
|
|
|
|
|
const deptId = id
|
|
const deptId = id
|
|
|
|
|
|
|
|
|
|
+// 查询参数
|
|
|
interface Query {
|
|
interface Query {
|
|
|
deptId?: number
|
|
deptId?: number
|
|
|
deviceName?: string
|
|
deviceName?: string
|
|
@@ -59,6 +29,12 @@ const query = ref<Query>({
|
|
|
]
|
|
]
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+// 分页相关参数
|
|
|
|
|
+const pageSize = ref(20) // 每页数量
|
|
|
|
|
+const lastTs = ref<string | null>(null) // 当前查询的游标 (上一页最后一条的时间)
|
|
|
|
|
+const historyStack = ref<(string | null)[]>([]) // 历史游标栈,用于实现“上一页”
|
|
|
|
|
+
|
|
|
|
|
+// 数据列表类型
|
|
|
interface ListItem {
|
|
interface ListItem {
|
|
|
deviceName: string
|
|
deviceName: string
|
|
|
serialNumber: string
|
|
serialNumber: string
|
|
@@ -68,51 +44,117 @@ interface ListItem {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const list = ref<ListItem[]>([])
|
|
const list = ref<ListItem[]>([])
|
|
|
-
|
|
|
|
|
const loading = ref(false)
|
|
const loading = ref(false)
|
|
|
|
|
|
|
|
|
|
+// --- 计算属性 ---
|
|
|
|
|
+
|
|
|
|
|
+// 判断是否选择了设备 (根据你的需求,这里主要判断 deviceCode)
|
|
|
|
|
+const isDeviceSelected = computed(() => {
|
|
|
|
|
+ return !!query.value.deviceCode
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 判断是否可以点击上一页
|
|
|
|
|
+const canGoBack = computed(() => historyStack.value.length > 0)
|
|
|
|
|
+// 判断是否可以点击下一页 (简单逻辑:当前列表数量等于每页数量,说明可能还有下一页)
|
|
|
|
|
+const canGoNext = computed(() => list.value.length === pageSize.value)
|
|
|
|
|
+
|
|
|
|
|
+// --- 核心逻辑 ---
|
|
|
|
|
+
|
|
|
const loadList = useDebounceFn(async function () {
|
|
const loadList = useDebounceFn(async function () {
|
|
|
|
|
+ // 如果没有选择设备,不请求,直接清空列表
|
|
|
|
|
+ if (!isDeviceSelected.value) {
|
|
|
|
|
+ list.value = []
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
loading.value = true
|
|
loading.value = true
|
|
|
try {
|
|
try {
|
|
|
const { time, ...other } = query.value
|
|
const { time, ...other } = query.value
|
|
|
|
|
|
|
|
- const data = await IotDeviceApi.getMonitoringQuery({
|
|
|
|
|
|
|
+ // 构造请求参数,加入分页参数
|
|
|
|
|
+ // 注意:这里假设后端接口支持 pageSize 和 lastTs 参数
|
|
|
|
|
+ // 如果后端字段名不同(例如 limit, offset 等),请在此处修改
|
|
|
|
|
+ const params = {
|
|
|
...other,
|
|
...other,
|
|
|
beginTime: time?.[0],
|
|
beginTime: time?.[0],
|
|
|
- endTime: time?.[1]
|
|
|
|
|
- })
|
|
|
|
|
- list.value = data.data
|
|
|
|
|
|
|
+ endTime: time?.[1],
|
|
|
|
|
+ pageSize: pageSize.value, // 每页条数
|
|
|
|
|
+ lastTs: lastTs.value // 游标
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const data = await IotDeviceApi.getMonitoringQuery(params)
|
|
|
|
|
+ list.value = data.data || []
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(error)
|
|
|
|
|
+ list.value = []
|
|
|
} finally {
|
|
} finally {
|
|
|
loading.value = false
|
|
loading.value = false
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+// 搜索按钮逻辑
|
|
|
function handleQuery() {
|
|
function handleQuery() {
|
|
|
|
|
+ if (!query.value.deviceCode) {
|
|
|
|
|
+ ElMessage.warning('请先输入设备编码')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 重置分页状态
|
|
|
|
|
+ lastTs.value = null
|
|
|
|
|
+ historyStack.value = []
|
|
|
|
|
+ loadList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 下一页
|
|
|
|
|
+function handleNext() {
|
|
|
|
|
+ if (list.value.length === 0) return
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 将当前游标推入栈中 (保存当前页的状态以便返回)
|
|
|
|
|
+ historyStack.value.push(lastTs.value)
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 获取列表最后一条数据的时间戳作为新的游标
|
|
|
|
|
+ // 假设 list 中包含 ts 字段,且数据是按时间排序的
|
|
|
|
|
+ const lastItem = list.value[list.value.length - 1]
|
|
|
|
|
+ if (lastItem && lastItem.ts) {
|
|
|
|
|
+ lastTs.value = lastItem.ts
|
|
|
|
|
+ loadList()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.warning('无法获取下一页游标')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 上一页
|
|
|
|
|
+function handlePrev() {
|
|
|
|
|
+ if (historyStack.value.length === 0) return
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 弹出栈顶元素作为游标
|
|
|
|
|
+ const prevTs = historyStack.value.pop()
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 设置并加载
|
|
|
|
|
+ lastTs.value = prevTs ?? null
|
|
|
|
|
+ loadList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 每页数量变化
|
|
|
|
|
+function handleSizeChange() {
|
|
|
|
|
+ // 切换大小时重置回第一页,避免游标错乱
|
|
|
|
|
+ lastTs.value = null
|
|
|
|
|
+ historyStack.value = []
|
|
|
loadList()
|
|
loadList()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// watch(
|
|
|
|
|
-// [
|
|
|
|
|
-// () => query.value.deptId,
|
|
|
|
|
-// () => query.value.deviceName,
|
|
|
|
|
-// () => query.value.deviceCode,
|
|
|
|
|
-// () => query.value.time
|
|
|
|
|
-// ],
|
|
|
|
|
-// () => {
|
|
|
|
|
-// handleQuery()
|
|
|
|
|
-// },
|
|
|
|
|
-// { immediate: true }
|
|
|
|
|
-// )
|
|
|
|
|
|
|
+const { ZmTable, ZmTableColumn } = useTableComponents<ListItem>()
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
<template>
|
|
|
<div
|
|
<div
|
|
|
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))]"
|
|
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))]"
|
|
|
>
|
|
>
|
|
|
- <!-- v-model="query.deptId" -->
|
|
|
|
|
|
|
+ <!-- 左侧部门树 -->
|
|
|
<div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2">
|
|
<div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2">
|
|
|
<DeptTreeSelect :top-id="156" :deptId="deptId" :init-select="false" :show-title="false" />
|
|
<DeptTreeSelect :top-id="156" :deptId="deptId" :init-select="false" :show-title="false" />
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 顶部搜索栏 -->
|
|
|
<el-form
|
|
<el-form
|
|
|
size="default"
|
|
size="default"
|
|
|
class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
|
|
class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
|
|
@@ -155,19 +197,100 @@ function handleQuery() {
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</div>
|
|
</div>
|
|
|
</el-form>
|
|
</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">
|
|
|
|
|
- <template #default="{ width, height }">
|
|
|
|
|
- <el-table-v2
|
|
|
|
|
- v-loading="loading"
|
|
|
|
|
- :columns="columns"
|
|
|
|
|
- :width="width"
|
|
|
|
|
- :height="height"
|
|
|
|
|
- :data="list"
|
|
|
|
|
- />
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-auto-resizer>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 表格区域 -->
|
|
|
|
|
+ <div class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col p-4 relative">
|
|
|
|
|
+ <!-- 场景1:未选择设备 -->
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-if="!isDeviceSelected"
|
|
|
|
|
+ class="flex-1 flex flex-col items-center justify-center text-gray-400"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Icon icon="ep:warning" class="text-6xl mb-4" />
|
|
|
|
|
+ <span class="text-lg">请先输入设备编码进行搜索</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 场景2:已选择设备 (显示表格) -->
|
|
|
|
|
+ <div v-else class="flex-1 flex flex-col h-full overflow-hidden">
|
|
|
|
|
+ <div class="flex-1 min-h-0">
|
|
|
|
|
+ <!-- 注意:这里使用了 el-auto-resizer 包裹 zm-table 以获得自适应高度 -->
|
|
|
|
|
+ <el-auto-resizer>
|
|
|
|
|
+ <template #default="{ width, height }">
|
|
|
|
|
+ <zm-table
|
|
|
|
|
+ :data="list"
|
|
|
|
|
+ :loading="loading"
|
|
|
|
|
+ :width="width"
|
|
|
|
|
+ :height="height"
|
|
|
|
|
+ :max-height="height"
|
|
|
|
|
+ >
|
|
|
|
|
+ <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"
|
|
|
|
|
+ />
|
|
|
|
|
+ <zm-table-column
|
|
|
|
|
+ prop="ts"
|
|
|
|
|
+ title="时间"
|
|
|
|
|
+ label="时间"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ min-width="160"
|
|
|
|
|
+ />
|
|
|
|
|
+ </zm-table>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-auto-resizer>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 自定义分页工具栏 -->
|
|
|
|
|
+ <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>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|