| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- <template>
- <div class="point-monitor-page">
- <div class="monitor-layout">
- <a-card :bordered="false" class="menu-panel" :body-style="{ padding: '8px 0' }">
- <a-menu :open-keys="openKeys" v-model:selectedKeys="selectedKeys" mode="inline" @title-click="handleChannelClick" @click="handleDeviceClick">
- <a-sub-menu v-for="channel in channels" :key="'c-' + channel.id">
- <template #title>{{ channel.name }}</template>
- <a-menu-item v-for="device in channel.deviceList || []" :key="'d-' + device.id">{{ device.name }}</a-menu-item>
- </a-sub-menu>
- </a-menu>
- </a-card>
- <div class="content-panel">
- <a-card :bordered="false">
- <a-form :model="queryParams" layout="inline">
- <a-form-item label="名称">
- <a-input v-model:value="queryParams.name" allow-clear placeholder="请输入名称" />
- </a-form-item>
- <a-form-item label="编码">
- <a-input v-model:value="queryParams.code" allow-clear placeholder="请输入编码" />
- </a-form-item>
- <a-form-item label="地址">
- <a-input-number v-model:value="queryParams.addr" :precision="0" :controls="false" placeholder="请输入地址" class="query-number" />
- </a-form-item>
- <a-form-item label="分类">
- <a-select v-model:value="queryParams.category" class="query-select" :options="categoryOptions" @change="handleCategoryChange" />
- </a-form-item>
- <a-form-item>
- <a-space>
- <a-button type="primary" :loading="loading" @click="handleQuery"><BearJiaIcon icon="SearchOutlined"/>查询</a-button>
- <a-button @click="resetQuery"><BearJiaIcon icon="ReloadOutlined"/>重置</a-button>
- <a-button v-if="countdown > 0" disabled>{{ countdown }}s后刷新数据</a-button>
- </a-space>
- </a-form-item>
- </a-form>
- </a-card>
- <a-card :bordered="false" class="section">
- <a-table
- :columns="columns"
- :data-source="rows"
- :loading="loading"
- :pagination="pagination"
- row-key="code"
- size="middle"
- :scroll="tableScroll"
- @change="handleTableChange"
- >
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'tmReport'">{{ formatTime(record.tmReport) }}</template>
- <template v-else-if="column.key === 'tmChange'">{{ formatTime(record.tmChange) }}</template>
- </template>
- </a-table>
- </a-card>
- </div>
- </div>
- </div>
- </template>
- <script setup name="PointMonitor">
- import { computed, onBeforeUnmount, reactive, ref } from 'vue';
- import dayjs from 'dayjs';
- import { cascadeMonitorChannel } from '@/api/config/channel';
- import { pointMonitor } from '@/api/config/point';
- import { BearJiaIcon } from '@/utils/BearJiaIcon.js';
- const CATEGORY_FIRST = 'YX';
- const categoryOptions = [
- { label: '遥信', value: 'YX', interval: 5 },
- { label: '遥测', value: 'YC', interval: 15 }
- // { label: '遥脉', value: 'YM' },
- // { label: '遥控', value: 'YK' },
- // { label: '遥调', value: 'YT' }
- ];
- const loading = ref(false);
- const rows = ref([]);
- const channels = ref([]);
- const openKeys = ref([]);
- const selectedKeys = ref([]);
- const currentDeviceId = ref(undefined);
- const queryParams = reactive({
- pageNum: 1,
- pageSize: 10,
- category: CATEGORY_FIRST,
- name: undefined,
- code: undefined,
- addr: undefined
- });
- const pagination = reactive({ current: 1, pageSize: 10, total: 0, showSizeChanger: true });
- // 自动刷新定时器: 按`分类`选中option的interval定时调用getList
- let dataRefreshInterval = null;
- // 倒计时秒表, 每1s递减
- let countdownInterval = null;
- // 距下次刷新的秒数, >0时显示倒计时按钮
- const countdown = ref(0);
- const currentCategoryOption = computed(() => categoryOptions.find((item) => item.value === queryParams.category));
- // 清空定时器和倒计时(x<=0时按钮隐藏)
- const clearDataRefresh = () => {
- if (dataRefreshInterval) {
- clearInterval(dataRefreshInterval);
- dataRefreshInterval = null;
- }
- if (countdownInterval) {
- clearInterval(countdownInterval);
- countdownInterval = null;
- }
- countdown.value = 0;
- };
- // 值列: 遥信(YX)取iValue, 遥测(YC)取dValue
- const columns = computed(() => [
- { title: '名称', dataIndex: 'name', width: 180, ellipsis: true },
- { title: '编码', dataIndex: 'code', width: 140, ellipsis: true },
- { title: '地址', dataIndex: 'addr', width: 100 },
- { title: '测点属性', dataIndex: 'pointAttr', width: 110 },
- { title: '值', dataIndex: queryParams.category === 'YX' ? 'ivalue' : 'dvalue', width: 120 },
- { title: '值属性', dataIndex: 'valueAttr', width: 110 },
- { title: '上报时间', dataIndex: 'tmReport', key: 'tmReport', width: 170 },
- { title: '变化时间', dataIndex: 'tmChange', key: 'tmChange', width: 170 }
- ]);
- const tableScroll = computed(() => ({ x: columns.value.reduce((sum, col) => sum + (col.width || 140), 0) + 40 }));
- // 计算2: 时间计算, timestamp 转 date(yyyy-MM-dd HH:mm:ss)
- const formatTime = (timestamp) => (timestamp && Number(timestamp) > 0 ? dayjs(Number(timestamp)).format('YYYY-MM-DD HH:mm:ss') : '');
- const getList = async () => {
- // pointMonitor执行前, 若定时器已存在先清空, 避免重复叠加
- clearDataRefresh();
- if (currentDeviceId.value === undefined) {
- rows.value = [];
- pagination.total = 0;
- return;
- }
- loading.value = true;
- try {
- const res = await pointMonitor({ ...queryParams, configDeviceId: currentDeviceId.value });
- rows.value = res?.rows || [];
- pagination.total = res?.total || 0;
- pagination.current = queryParams.pageNum;
- pagination.pageSize = queryParams.pageSize;
- // 加载成功后, 按`分类`选中option的interval创建定时器并初始化倒计时; 失败即停不重建
- const interval = currentCategoryOption.value?.interval;
- if (interval) {
- countdown.value = interval;
- countdownInterval = setInterval(() => {
- if (countdown.value > 0) countdown.value -= 1;
- }, 1000);
- dataRefreshInterval = setInterval(getList, interval * 1000);
- }
- } catch {
- // 请求异常由axios拦截器统一提示, 此处失败即停
- } finally {
- loading.value = false;
- }
- };
- // 重置右侧筛选条件: 分类回到第一个option, 清空名称/编码/地址
- const resetFilter = () => {
- queryParams.category = CATEGORY_FIRST;
- queryParams.name = undefined;
- queryParams.code = undefined;
- queryParams.addr = undefined;
- queryParams.pageNum = 1;
- };
- // 选中设备并刷新数据
- const selectDevice = (device) => {
- currentDeviceId.value = device?.id;
- selectedKeys.value = device ? ['d-' + device.id] : [];
- resetFilter();
- getList();
- };
- // 事件1: 点击1级菜单 — 展开选中的通道、收起其他、选中第一个二级菜单、重置筛选并刷新
- const handleChannelClick = ({ key }) => {
- openKeys.value = [key];
- const channel = channels.value.find((item) => 'c-' + item.id === key);
- selectDevice(channel?.deviceList?.[0]);
- };
- // 事件2: 点击2级菜单 — 切换设备、分类回到第一个option、清空条件、重新加载
- const handleDeviceClick = ({ key }) => {
- if (!key.startsWith('d-')) return;
- const deviceId = Number(key.slice(2));
- const device = channels.value.flatMap((channel) => channel.deviceList || []).find((item) => item.id === deviceId);
- currentDeviceId.value = deviceId;
- resetFilter();
- getList();
- };
- // 事件4: 切换分类 — 条件分类置为当前选中值、清空名称/编码/地址、重新加载
- const handleCategoryChange = () => {
- queryParams.name = undefined;
- queryParams.code = undefined;
- queryParams.addr = undefined;
- queryParams.pageNum = 1;
- getList();
- };
- // 事件5: 查询数据
- const handleQuery = () => {
- queryParams.pageNum = 1;
- getList();
- };
- // 事件6: 重制搜索条件 — 清空名称/编码/地址
- const resetQuery = () => {
- queryParams.name = undefined;
- queryParams.code = undefined;
- queryParams.addr = undefined;
- };
- // 事件3: 分页查询
- const handleTableChange = (page) => {
- queryParams.pageNum = page.current;
- queryParams.pageSize = page.pageSize;
- getList();
- };
- const loadCascades = async () => {
- const res = await cascadeMonitorChannel();
- channels.value = res?.data || [];
- // 默认选中第一个一级菜单的第一个二级菜单
- const firstChannel = channels.value[0];
- if (firstChannel) {
- openKeys.value = ['c-' + firstChannel.id];
- selectDevice(firstChannel.deviceList?.[0]);
- } else {
- getList();
- }
- };
- // 浏览器tab隐藏时清空定时器, 恢复可见时立即刷新并重启倒计时
- const handleVisibilityChange = () => {
- if (document.hidden) {
- clearDataRefresh();
- } else {
- getList();
- }
- };
- document.addEventListener('visibilitychange', handleVisibilityChange);
- // 页面销毁时清空定时器
- onBeforeUnmount(() => {
- clearDataRefresh();
- document.removeEventListener('visibilitychange', handleVisibilityChange);
- });
- loadCascades();
- </script>
- <style lang="less" scoped>
- .point-monitor-page {
- .monitor-layout {
- display: flex;
- align-items: flex-start;
- }
- .menu-panel {
- flex: 0 0 200px;
- width: 200px;
- margin-right: 12px;
- :deep(.ant-menu) {
- border-inline-end: none;
- }
- }
- .content-panel {
- flex: 1;
- min-width: 0;
- }
- .section {
- margin-top: 12px;
- }
- .query-select {
- width: 140px;
- }
- .query-number {
- width: 120px;
- }
- }
- </style>
|