| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- <script lang="ts" setup>
- import { IotDeviceApi } from '@/api/pms/device'
- import { useUserStore } from '@/store/modules/user'
- import download from '@/utils/download'
- import { rangeShortcuts } from '@/utils/formatTime'
- import { formatIotValue } from '@/utils/useSocketBus'
- import { useDebounceFn } from '@vueuse/core'
- import { dayjs, ElMessage } from 'element-plus'
- import { computed, ref } from 'vue'
- defineOptions({ name: 'MonitoringQuery' })
- const id = useUserStore().getUser.deptId ?? 157
- const deptId = id
- interface Query {
- deptId?: number
- deviceCode?: string
- time?: string[]
- identifier?: string
- }
- const query = ref<Query>({
- deviceCode: '',
- time: [...rangeShortcuts[0].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))]
- })
- const pageSize = ref(100)
- const historyStack = ref<string[]>([])
- const hasSearched = ref(false)
- // 数据列表类型
- interface ListItem {
- deviceName: string
- serialNumber: string
- identity: string
- value: string
- ts: string
- }
- 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.deviceCode
- return hasTime && hasIdentity
- })
- const canGoBack = computed(() => historyStack.value.length > 0)
- const canGoNext = computed(() => list.value.length >= pageSize.value)
- const deviceOptions = ref<{ label: string; value: string; raw: any }[]>([])
- const optionsLoading = ref(false)
- const loadOptions = useDebounceFn(async function () {
- handleReset()
- try {
- optionsLoading.value = true
- const data = await IotDeviceApi.getDevice({
- deptId: query.value.deptId
- })
- deviceOptions.value = data.map((item: any) => {
- return {
- label: item.deviceCode + '-' + item.deviceName,
- raw: item,
- value: item.deviceCode
- }
- })
- } catch (error) {
- console.error(error)
- } finally {
- optionsLoading.value = false
- }
- }, 300)
- const attributeLoading = ref(false)
- const attributeOptions = ref<{ label: string; value: string; raw: any }[]>([])
- const handleNodeClick = (data: any) => {
- query.value.deptId = data.id
- loadOptions()
- }
- onMounted(() => {
- loadOptions()
- })
- const loadAttrOptions = useDebounceFn(async function (id: any) {
- attributeOptions.value = []
- query.value.identifier = undefined
- try {
- attributeLoading.value = true
- const data = await IotDeviceApi.getIotDeviceTds(Number(id))
- attributeOptions.value = data.map((item: any) => {
- return {
- label: item.modelName,
- raw: item,
- value: item.identifier
- }
- })
- } catch (error) {
- console.error(error)
- } finally {
- attributeLoading.value = false
- }
- }, 300)
- function handleDeviceChange(value: string) {
- const option = deviceOptions.value.find((i) => i.value === value)
- if (option) {
- loadAttrOptions(option.raw.id)
- }
- }
- const loadList = useDebounceFn(async function () {
- if (!isConditionValid.value) {
- // list.value = []
- return
- }
- loading.value = true
- try {
- 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 handleQuery() {
- if (!query.value.deviceCode) {
- ElMessage.warning('请选择设备')
- return
- }
- if (!query.value.time || query.value.time.length !== 2) {
- ElMessage.warning('请选择时间范围')
- return
- }
- hasSearched.value = true
- historyStack.value = []
- loadList()
- }
- function handleReset() {
- query.value = {
- deviceCode: '',
- time: [...rangeShortcuts[0].value().map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))]
- }
- 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
- const prevEndTime = historyStack.value.pop()
- if (prevEndTime) {
- query.value.time = [query.value.time[0], dayjs(prevEndTime).format('YYYY-MM-DD HH:mm:ss')]
- loadList()
- }
- }
- function handleSizeChange() {
- if (isConditionValid.value) {
- historyStack.value = []
- loadList()
- }
- }
- const exportLoading = ref(false)
- const handleExport = useDebounceFn(async function () {
- if (!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)
- function formatterValue(row: ListItem) {
- const { value, suffix, isText } = formatIotValue(row.value)
- if (isText) {
- return value
- }
- return `${Number(value).toFixed(2)}${suffix}`
- }
- </script>
- <template>
- <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))]"
- >
- <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"
- @node-click="handleNodeClick"
- />
- </div>
- <el-form
- size="default"
- class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 gap-8 flex items-center justify-between"
- >
- <div class="flex items-center gap-8">
- <el-form-item label="设备">
- <el-select
- :loading="optionsLoading"
- v-model="query.deviceCode"
- :options="deviceOptions"
- placeholder="请选择设备"
- class="w-60!"
- @change="handleDeviceChange"
- />
- </el-form-item>
- <el-form-item label="时间">
- <el-date-picker
- v-model="query.time"
- value-format="YYYY-MM-DD HH:mm:ss"
- type="datetimerange"
- start-placeholder="开始日期"
- end-placeholder="结束日期"
- :shortcuts="rangeShortcuts"
- :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
- class="!w-360px"
- />
- </el-form-item>
- <el-form-item label="属性">
- <el-select
- :loading="attributeLoading"
- v-model="query.identifier"
- :options="attributeOptions"
- placeholder="请选择属性"
- :no-data-text="!query.deviceCode ? '请先选择设备' : '暂无数据'"
- class="w-60!"
- />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleQuery()">
- <Icon icon="ep:search" 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">
- <template #default="{ width, height }">
- <zm-table
- :data="list"
- :loading="loading"
- :width="width"
- :height="height"
- :max-height="height"
- >
- <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"
- coverFormatter
- :real-value="formatterValue"
- />
- <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-[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>
- </template>
- <style scoped>
- :deep(.el-form-item) {
- margin-bottom: 0;
- }
- </style>
|