| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- <script setup lang="ts">
- 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 { useDebounceFn } from '@vueuse/core'
- defineOptions({ name: 'MonitoringList' })
- const { t } = useI18n()
- const id = useUserStore().getUser.deptId ?? 157
- const deptId = id
- interface Query {
- deptId?: number
- deviceName?: string
- deviceCode?: string
- ifInline?: string
- pageNo: number
- pageSize: number
- }
- const viewMode = ref('card')
- const query = ref<Query>({
- pageNo: 1,
- pageSize: 12,
- deptId: id,
- ifInline: '3'
- })
- interface OliDevice {
- id: number
- carId?: number
- deviceName: string
- deviceCode: string
- assetClassName: string
- deviceStatus: string
- ifInline: number
- lastInlineTime: string
- carOnline: string
- deptName: string
- vehicleName: string
- }
- const list = ref<OliDevice[]>([])
- const total = ref(0)
- const loading = ref(false)
- const loadList = useDebounceFn(async function () {
- 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
- } finally {
- loading.value = false
- }
- })
- function handleSizeChange(val: number) {
- query.value.pageSize = val
- handleQuery()
- }
- function handleCurrentChange(val: number) {
- query.value.pageNo = val
- loadList()
- }
- function handleQuery(setPage = true) {
- if (setPage) {
- query.value.pageNo = 1
- }
- loadList()
- }
- function resetQuery() {
- query.value = {
- pageNo: 1,
- pageSize: 12,
- deptId: id
- }
- handleQuery()
- }
- watch(
- [
- () => query.value.deptId,
- () => query.value.deviceName,
- () => query.value.deviceCode,
- () => query.value.ifInline
- ],
- () => {
- handleQuery()
- },
- { immediate: true }
- )
- const { ZmTable, ZmTableColumn } = useTableComponents<OliDevice>()
- const getStatusConfig = (status: number) => {
- switch (status) {
- case 3: // 在线
- return {
- bg: 'bg-gradient-to-br from-emerald-400 to-cyan-500',
- icon: 'ep:success-filled',
- label: '在线',
- textColor: 'text-emerald-600'
- }
- case 4: // 离线
- return {
- bg: 'bg-gradient-to-br from-slate-400 to-slate-500',
- icon: 'ep:circle-close-filled',
- label: '离线',
- textColor: 'text-slate-500'
- }
- case 5: // 未激活
- return {
- bg: 'bg-gradient-to-br from-blue-400 to-indigo-500',
- icon: 'ep:info-filled',
- label: '未激活',
- textColor: 'text-blue-500'
- }
- case 2: // 禁用
- return {
- bg: 'bg-gradient-to-br from-orange-400 to-red-500',
- icon: 'ep:warn-triangle-filled',
- label: '禁用',
- textColor: 'text-red-500'
- }
- default:
- return {
- bg: 'bg-gradient-to-br from-gray-300 to-gray-400',
- icon: 'ep:question-filled',
- label: '未知',
- textColor: 'text-gray-500'
- }
- }
- }
- const message = useMessage()
- const router = useRouter()
- const openDetail = (
- id: number,
- ifInline: number,
- time: string,
- name: string,
- code: string,
- dept: string,
- vehicle: string,
- carOnline: string
- ) => {
- if (time === null || time === undefined) {
- message.warning('没有数采数据')
- return
- }
- router.push({
- name: 'MonitoringDetail',
- query: { id, ifInline, carOnline, time, name, code, dept, vehicle }
- })
- }
- </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"
- v-model="query.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"
- >
- <div class="flex items-center gap-8">
- <el-form-item :label="t('monitor.deviceName')">
- <el-input
- v-model="query.deviceName"
- :placeholder="t('monitor.nameHolder')"
- clearable
- @keyup.enter="handleQuery()"
- class="!w-240px"
- />
- </el-form-item>
- <el-form-item :label="t('monitor.deviceCode')">
- <el-input
- v-model="query.deviceCode"
- :placeholder="t('monitor.codeHolder')"
- clearable
- @keyup.enter="handleQuery()"
- 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>
- <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-form-item>
- </div>
- <el-form-item>
- <el-button-group>
- <el-button :type="viewMode === 'card' ? 'primary' : 'default'" @click="viewMode = 'card'">
- <Icon icon="ep:grid" />
- </el-button>
- <el-button :type="viewMode === 'list' ? 'primary' : 'default'" @click="viewMode = 'list'">
- <Icon icon="ep:list" />
- </el-button>
- </el-button-group>
- </el-form-item>
- </el-form>
- <div
- class="bg-white dark:bg-[#1d1e1f] shadow rounded-lg flex flex-col"
- :class="{ 'p-4': viewMode === 'list' }"
- >
- <div class="flex-1 relative">
- <el-auto-resizer class="absolute">
- <template #default="{ width, height }">
- <template v-if="list.length > 0">
- <zm-table
- v-if="viewMode === 'list'"
- :data="list"
- :loading="loading"
- :width="width"
- :max-height="height"
- :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')" />
- <zm-table-column :label="t('monitor.operation')" :width="60">
- <template #default="scope">
- <el-button
- link
- type="primary"
- @click="
- openDetail(
- scope.row.id,
- scope.row.ifInline,
- scope.row.lastInlineTime,
- scope.row.deviceName,
- scope.row.deviceCode,
- scope.row.deptName,
- scope.row.vehicleName,
- scope.row.carOnline ?? ''
- )
- "
- >
- {{ t('monitor.check') }}
- </el-button>
- </template>
- </zm-table-column>
- </zm-table>
- <el-scrollbar
- v-else
- :height="height"
- :class="width"
- view-class="grid grid-cols-4 grid-rows-3 gap-4 p-4"
- >
- <div
- v-for="item in list"
- :key="item.id"
- class="group relative flex flex-col bg-white dark:bg-[#262727] rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 hover:shadow-[0_8px_20px_rgba(0,0,0,0.1)] hover:-translate-y-1 transition-all duration-300 overflow-hidden"
- >
- <div
- class="h-[80px] px-4 flex items-center justify-between overflow-hidden"
- :class="getStatusConfig(item.ifInline).bg"
- >
- <div class="flex items-center gap-3 z-10 max-w-[80%]">
- <div
- class="bg-white/20 p-2 rounded-lg backdrop-blur-md shadow-inner shrink-0"
- >
- <Icon :icon="item.carId ? 'ep:van' : 'ep:cpu'" class="text-xl text-white" />
- </div>
- <!-- 文本区域 -->
- <div class="flex flex-col overflow-hidden">
- <el-tooltip effect="dark" :content="item.deviceName" placement="top-start">
- <span
- class="text-white font-bold text-base leading-tight"
- :title="item.deviceName"
- >
- {{ item.deviceName }}
- </span>
- </el-tooltip>
- <span class="text-white/80 text-xs font-mono truncate mt-0.5">
- {{ item.deviceCode }}
- </span>
- </div>
- </div>
- <div class="z-10 shrink-0">
- <div
- class="flex items-center gap-1.5 bg-black/20 backdrop-blur-md px-2.5 py-1 rounded-full text-xs font-medium text-white shadow-sm border border-white/10"
- >
- <div
- class="w-1.5 h-1.5 rounded-full bg-white animate-pulse"
- v-if="item.ifInline === 3"
- >
- </div>
- <Icon :icon="getStatusConfig(item.ifInline).icon" v-else />
- <span>{{ getStatusConfig(item.ifInline).label }}</span>
- </div>
- </div>
- </div>
- <!-- 内容区域 -->
- <div
- class="flex-1 p-4 flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-300 relative z-20 bg-white dark:bg-[#262727]"
- >
- <!-- 编码行 -->
- <div class="flex items-center justify-between pb-2">
- <span class="text-gray-400 text-xs flex items-center gap-1.5">
- <Icon icon="ep:postcard" /> 设备编码
- </span>
- <span
- class="font-mono font-medium truncate max-w-[140px] select-all"
- :title="item.deviceCode"
- >
- {{ item.deviceCode }}
- </span>
- </div>
- <!-- 类别行 -->
- <div class="flex items-center justify-between pb-2">
- <span class="text-gray-400 text-xs flex items-center gap-1.5">
- <Icon icon="ep:price-tag" /> 设备类别
- </span>
- <el-tag
- size="small"
- type="info"
- effect="light"
- round
- class="!bg-gray-100 dark:!bg-gray-800 !border-gray-200 dark:!border-gray-600"
- >
- {{ item.assetClassName || '-' }}
- </el-tag>
- </div>
- <!-- 时间行 -->
- <div class="flex items-center justify-between">
- <span class="text-gray-400 text-xs flex items-center gap-1.5">
- <Icon icon="ep:clock" /> 最后上线
- </span>
- <span
- class="text-xs font-medium"
- :class="
- item.lastInlineTime ? 'text-gray-600 dark:text-gray-300' : 'text-gray-300'
- "
- >
- {{ item.lastInlineTime || '暂无记录' }}
- </span>
- </div>
- </div>
- <!-- 底部操作栏 -->
- <div
- class="px-4 py-2.5 bg-gray-50/80 dark:bg-[#1d1e1f] flex justify-between items-center group-hover:bg-blue-50/30 dark:group-hover:bg-blue-900/10 transition-colors"
- >
- <span class="text-[10px] text-gray-400"></span>
- <!-- <span class="text-[10px] text-gray-400">ID: {{ item.id }}</span> -->
- <el-button type="primary" link size="small" class="!px-0 group/btn">
- <span
- class="mr-1 group-hover/btn:underline"
- @click="
- openDetail(
- item.id,
- item.ifInline,
- item.lastInlineTime,
- item.deviceName,
- item.deviceCode,
- item.deptName,
- item.vehicleName,
- item.carOnline ?? ''
- )
- "
- >查看详情</span
- >
- <Icon
- icon="ep:arrow-right"
- class="group-hover/btn:translate-x-1 transition-transform"
- />
- </el-button>
- </div>
- </div>
- </el-scrollbar>
- </template>
- <div
- v-else
- :style="{ width: width + 'px', height: height + 'px' }"
- class="flex flex-col items-center justify-center text-gray-400"
- >
- <div class="i-lucide-inbox text-5xl mb-4 op-50"></div>
- <p class="text-sm font-medium">暂无相关数据</p>
- <p class="text-xs mt-1 op-60">尝试调整过滤条件或刷新页面</p>
- </div>
- </template>
- </el-auto-resizer>
- </div>
- <div
- class="h-10 mt-4 flex items-center justify-end"
- :class="{ 'mb-4 px-4': viewMode === 'card' }"
- >
- <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>
- </div>
- </div>
- </template>
- <style scoped>
- :deep(.el-form-item) {
- margin-bottom: 0;
- }
- </style>
|