|
@@ -1,6 +1,5 @@
|
|
|
<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'
|
|
@@ -9,11 +8,9 @@ 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
|
|
@@ -24,15 +21,13 @@ interface Query {
|
|
|
const query = ref<Query>({
|
|
const query = ref<Query>({
|
|
|
deviceCode: 'YF6660355',
|
|
deviceCode: 'YF6660355',
|
|
|
time: [
|
|
time: [
|
|
|
- dayjs().subtract(5, 'minute').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
|
|
|
|
+ dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
dayjs().format('YYYY-MM-DD HH:mm:ss')
|
|
dayjs().format('YYYY-MM-DD HH:mm:ss')
|
|
|
]
|
|
]
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-// 分页相关参数
|
|
|
|
|
-const pageSize = ref(20) // 每页数量
|
|
|
|
|
-const lastTs = ref<string | null>(null) // 当前查询的游标 (上一页最后一条的时间)
|
|
|
|
|
-const historyStack = ref<(string | null)[]>([]) // 历史游标栈,用于实现“上一页”
|
|
|
|
|
|
|
+const pageSize = ref(100)
|
|
|
|
|
+const historyStack = ref<string[]>([]) // 存储历史开始时间
|
|
|
|
|
|
|
|
// 数据列表类型
|
|
// 数据列表类型
|
|
|
interface ListItem {
|
|
interface ListItem {
|
|
@@ -46,23 +41,14 @@ interface ListItem {
|
|
|
const list = ref<ListItem[]>([])
|
|
const list = ref<ListItem[]>([])
|
|
|
const loading = ref(false)
|
|
const loading = ref(false)
|
|
|
|
|
|
|
|
-// --- 计算属性 ---
|
|
|
|
|
|
|
+const isDeviceSelected = computed(() => !!query.value.deviceCode)
|
|
|
|
|
|
|
|
-// 判断是否选择了设备 (根据你的需求,这里主要判断 deviceCode)
|
|
|
|
|
-const isDeviceSelected = computed(() => {
|
|
|
|
|
- return !!query.value.deviceCode
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-// 判断是否可以点击上一页
|
|
|
|
|
const canGoBack = computed(() => historyStack.value.length > 0)
|
|
const canGoBack = computed(() => historyStack.value.length > 0)
|
|
|
-// 判断是否可以点击下一页 (简单逻辑:当前列表数量等于每页数量,说明可能还有下一页)
|
|
|
|
|
-const canGoNext = computed(() => list.value.length === pageSize.value)
|
|
|
|
|
|
|
|
|
|
-// --- 核心逻辑 ---
|
|
|
|
|
|
|
+const canGoNext = computed(() => list.value.length >= pageSize.value)
|
|
|
|
|
|
|
|
const loadList = useDebounceFn(async function () {
|
|
const loadList = useDebounceFn(async function () {
|
|
|
- // 如果没有选择设备,不请求,直接清空列表
|
|
|
|
|
- if (!isDeviceSelected.value) {
|
|
|
|
|
|
|
+ if (!isDeviceSelected.value || !query.value.time?.length) {
|
|
|
list.value = []
|
|
list.value = []
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
@@ -71,15 +57,12 @@ const loadList = useDebounceFn(async function () {
|
|
|
try {
|
|
try {
|
|
|
const { time, ...other } = query.value
|
|
const { time, ...other } = query.value
|
|
|
|
|
|
|
|
- // 构造请求参数,加入分页参数
|
|
|
|
|
- // 注意:这里假设后端接口支持 pageSize 和 lastTs 参数
|
|
|
|
|
- // 如果后端字段名不同(例如 limit, offset 等),请在此处修改
|
|
|
|
|
const params = {
|
|
const params = {
|
|
|
...other,
|
|
...other,
|
|
|
|
|
+ // 直接使用 query 中的时间
|
|
|
beginTime: time?.[0],
|
|
beginTime: time?.[0],
|
|
|
endTime: time?.[1],
|
|
endTime: time?.[1],
|
|
|
- pageSize: pageSize.value, // 每页条数
|
|
|
|
|
- lastTs: lastTs.value // 游标
|
|
|
|
|
|
|
+ pageSize: pageSize.value
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const data = await IotDeviceApi.getMonitoringQuery(params)
|
|
const data = await IotDeviceApi.getMonitoringQuery(params)
|
|
@@ -92,69 +75,76 @@ const loadList = useDebounceFn(async function () {
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-// 搜索按钮逻辑
|
|
|
|
|
function handleQuery() {
|
|
function handleQuery() {
|
|
|
if (!query.value.deviceCode) {
|
|
if (!query.value.deviceCode) {
|
|
|
ElMessage.warning('请先输入设备编码')
|
|
ElMessage.warning('请先输入设备编码')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- // 重置分页状态
|
|
|
|
|
- lastTs.value = null
|
|
|
|
|
|
|
+ if (!query.value.time || query.value.time.length !== 2) {
|
|
|
|
|
+ ElMessage.warning('请选择时间范围')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 重置历史栈
|
|
|
historyStack.value = []
|
|
historyStack.value = []
|
|
|
|
|
+
|
|
|
loadList()
|
|
loadList()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 下一页
|
|
|
|
|
function handleNext() {
|
|
function handleNext() {
|
|
|
if (list.value.length === 0) return
|
|
if (list.value.length === 0) return
|
|
|
|
|
+ if (!query.value.time || query.value.time.length < 2) return
|
|
|
|
|
|
|
|
- // 1. 将当前游标推入栈中 (保存当前页的状态以便返回)
|
|
|
|
|
- historyStack.value.push(lastTs.value)
|
|
|
|
|
-
|
|
|
|
|
- // 2. 获取列表最后一条数据的时间戳作为新的游标
|
|
|
|
|
- // 假设 list 中包含 ts 字段,且数据是按时间排序的
|
|
|
|
|
|
|
+ // 1. 获取当前页最后一条数据的时间
|
|
|
const lastItem = list.value[list.value.length - 1]
|
|
const lastItem = list.value[list.value.length - 1]
|
|
|
|
|
+
|
|
|
if (lastItem && lastItem.ts) {
|
|
if (lastItem && lastItem.ts) {
|
|
|
- lastTs.value = lastItem.ts
|
|
|
|
|
|
|
+ // 2. 将当前的【开始时间】存入栈中,以便返回
|
|
|
|
|
+ historyStack.value.push(query.value.time[1])
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 直接修改 query.time 的开始时间,保持结束时间不变
|
|
|
|
|
+ // 注意:重新赋值数组以触发响应式更新
|
|
|
|
|
+ query.value.time = [query.value.time[0], dayjs(lastItem.ts).format('YYYY-MM-DD HH:mm:ss')]
|
|
|
|
|
+
|
|
|
loadList()
|
|
loadList()
|
|
|
} else {
|
|
} else {
|
|
|
- ElMessage.warning('无法获取下一页游标')
|
|
|
|
|
|
|
+ ElMessage.warning('无法获取最后一条数据的时间,无法跳转')
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 上一页
|
|
|
|
|
function handlePrev() {
|
|
function handlePrev() {
|
|
|
if (historyStack.value.length === 0) return
|
|
if (historyStack.value.length === 0) return
|
|
|
|
|
+ if (!query.value.time || query.value.time.length < 2) return
|
|
|
|
|
|
|
|
- // 1. 弹出栈顶元素作为游标
|
|
|
|
|
- const prevTs = historyStack.value.pop()
|
|
|
|
|
|
|
+ // 1. 弹出上一次的开始时间
|
|
|
|
|
+ const prevEndTime = historyStack.value.pop()
|
|
|
|
|
|
|
|
- // 2. 设置并加载
|
|
|
|
|
- lastTs.value = prevTs ?? null
|
|
|
|
|
- loadList()
|
|
|
|
|
|
|
+ if (prevEndTime) {
|
|
|
|
|
+ // 2. 恢复 query.time 的开始时间
|
|
|
|
|
+ query.value.time = [query.value.time[0], dayjs(prevEndTime).format('YYYY-MM-DD HH:mm:ss')]
|
|
|
|
|
+
|
|
|
|
|
+ loadList()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 每页数量变化
|
|
|
|
|
function handleSizeChange() {
|
|
function handleSizeChange() {
|
|
|
- // 切换大小时重置回第一页,避免游标错乱
|
|
|
|
|
- lastTs.value = null
|
|
|
|
|
- historyStack.value = []
|
|
|
|
|
- loadList()
|
|
|
|
|
|
|
+ // 切换分页大小时,为了逻辑简单,直接重新查询第一页
|
|
|
|
|
+ if (isDeviceSelected.value && query.value.time) {
|
|
|
|
|
+ // 清空栈,因为分页切片改变了,之前的历史游标可能不再准确
|
|
|
|
|
+ historyStack.value = []
|
|
|
|
|
+ loadList()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-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))]"
|
|
|
>
|
|
>
|
|
|
- <!-- 左侧部门树 -->
|
|
|
|
|
<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"
|
|
@@ -198,98 +188,73 @@ const { ZmTable, ZmTableColumn } = useTableComponents<ListItem>()
|
|
|
</div>
|
|
</div>
|
|
|
</el-form>
|
|
</el-form>
|
|
|
|
|
|
|
|
- <!-- 表格区域 -->
|
|
|
|
|
- <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 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 }">
|
|
|
|
|
+ <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="时间"
|
|
|
|
|
+ :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>
|
|
|
|
|
|
|
|
- <!-- 场景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
|
|
|
|
|
+ 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>
|
|
|
|
|
|
|
|
- <!-- 自定义分页工具栏 -->
|
|
|
|
|
- <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 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>
|