Files
ndm-web-platform/src/pages/call-log-page.vue
yangsy 37781216b2 refactor: 重构项目结构
- 优化 `车站-设备-告警`  轮询机制
- 改进设备卡片的布局
- 支持修改设备
- 告警轮询中获取完整告警数据
- 车站告警详情支持导出完整的 `今日告警列表`
- 支持将状态持久化到 `IndexedDB`
- 新增轮询控制 (调试模式)
- 新增离线开发模式 (调试模式)
- 新增 `IndexedDB` 数据控制 (调试模式)
2025-12-11 13:42:22 +08:00

259 lines
8.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { exportCallLogApi, pageCallLogApi, type NdmCallLog, type NdmCallLogResultVO, type PageQueryExtra, type Station } from '@/apis';
import { useStationStore } from '@/stores';
import { downloadByData, parseErrorFeedback } from '@/utils';
import { useMutation } from '@tanstack/vue-query';
import dayjs from 'dayjs';
import {
NButton,
NDataTable,
NDatePicker,
NFlex,
NForm,
NFormItemGi,
NGrid,
NGridItem,
NSelect,
NTag,
type DataTableColumns,
type DataTableRowData,
type PaginationProps,
type SelectOption,
} from 'naive-ui';
import { storeToRefs } from 'pinia';
import { h } from 'vue';
import { computed, reactive, ref, watch, watchEffect } from 'vue';
interface SearchFields extends PageQueryExtra<NdmCallLog> {
stationCode?: Station['code'];
createdTime: [string, string];
}
const stationStore = useStationStore();
const { stations, onlineStations } = storeToRefs(stationStore);
const stationSelectOptions = computed(() => {
return stations.value.map<SelectOption>((station) => ({
label: station.name,
value: station.code,
disabled: !station.online,
}));
});
const searchFields = ref<SearchFields>({
createdTime: [dayjs().startOf('date').subtract(1, 'week').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('date').format('YYYY-MM-DD HH:mm:ss')],
});
const resetSearchFields = () => {
searchFields.value = {
stationCode: stations.value.find((station) => station.online)?.code,
createdTime: [dayjs().startOf('date').subtract(1, 'week').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('date').format('YYYY-MM-DD HH:mm:ss')],
};
};
const getExtraFields = (): PageQueryExtra<NdmCallLog> => {
const createdTime_precisest = searchFields.value.createdTime[0];
const createdTime_preciseed = searchFields.value.createdTime[1];
const sourceGbId_like = searchFields.value.sourceGbId_like;
const targetGbId_like = searchFields.value.targetGbId_like;
const method_like = searchFields.value.method_like;
const messageType_like = searchFields.value.messageType_like;
const cmdType_like = searchFields.value.cmdType_like;
return {
createdTime_precisest,
createdTime_preciseed,
sourceGbId_like,
targetGbId_like,
method_like,
messageType_like,
cmdType_like,
};
};
const searchFieldsChanged = ref(false);
watch(searchFields, () => {
searchFieldsChanged.value = true;
});
const tableColumns: DataTableColumns<NdmCallLogResultVO> = [
{ title: '时间', key: 'createdTime' },
{ title: '调用者国标码', key: 'sourceGbId' },
{ title: '被调用设备国标码', key: 'targetGbId' },
{ title: '调用方法', key: 'method' },
{ title: '消息类型', key: 'messageType' },
{ title: '操作类型', key: 'cmdType' },
];
const tableData = ref<DataTableRowData[]>([]);
const DEFAULT_PAGE_SIZE = 10;
const pagination = reactive<PaginationProps>({
showSizePicker: true,
page: 1,
pageSize: DEFAULT_PAGE_SIZE,
pageSizes: [5, 10, 20, 50, 80, 100],
itemCount: 0,
prefix: ({ itemCount }) => {
return h('div', {}, { default: () => `${itemCount}` });
},
onUpdatePage: (page: number) => {
pagination.page = page;
getTableData();
},
onUpdatePageSize: (pageSize: number) => {
pagination.pageSize = pageSize;
pagination.page = 1;
getTableData();
},
});
const { mutate: getTableData, isPending: tableLoading } = useMutation({
mutationFn: async () => {
if (!searchFields.value.stationCode) throw Error('请选择车站');
const res = await pageCallLogApi(
{
model: {},
extra: getExtraFields(),
current: pagination.page ?? 1,
size: pagination.pageSize ?? DEFAULT_PAGE_SIZE,
order: 'descending',
sort: 'id',
},
{
stationCode: searchFields.value.stationCode,
},
);
return res;
},
onSuccess: (res) => {
const { records, size, total } = res;
pagination.pageSize = parseInt(size);
pagination.itemCount = parseInt(total);
tableData.value = records;
},
onError: (error) => {
console.error(error);
const errorFeedback = parseErrorFeedback(error);
window.$message.error(errorFeedback);
},
});
const onClickReset = () => {
resetSearchFields();
pagination.page = 1;
pagination.pageSize = DEFAULT_PAGE_SIZE;
pagination.itemCount = 0;
getTableData();
};
const onClickQuery = () => {
if (searchFieldsChanged.value) {
pagination.page = 1;
pagination.pageSize = DEFAULT_PAGE_SIZE;
searchFieldsChanged.value = false;
}
getTableData();
};
const { mutate: exportTableData, isPending: exporting } = useMutation({
mutationFn: async () => {
if (!searchFields.value.stationCode) throw Error('请选择车站');
const data = await exportCallLogApi(
{
model: {},
extra: getExtraFields(),
current: pagination.page ?? 1,
size: pagination.pageSize ?? 10,
order: 'descending',
sort: 'id',
},
{
stationCode: searchFields.value.stationCode,
},
);
return data;
},
onSuccess: (data) => {
const time = dayjs().format('YYYY-MM-DD_HH-mm-ss');
downloadByData(data, `上级调用日志_${time}.xlsx`);
},
onError: (error) => {
console.error(error);
const errorFeedback = parseErrorFeedback(error);
window.$message.error(errorFeedback);
},
});
// 进入页面时选择首个在线的车站
// 当页面刷新时车站列表还没有加载完成defaultStation不存在if条件为false
// 等待车站列表加载完成后defaultStation会更新if条件为true会更新选择的车站并加载日志记录。
// 当从其他页面跳转过来时车站列表已经存在defaultStation存在if条件为true会更新选择的车站并加载日志记录。
// 由于if条件是and逻辑所以即使defaultStation因车站状态变化而更新由于已经选择了车站所以if条件为false。
const defaultStation = computed(() => onlineStations.value.at(0));
watchEffect(() => {
if (defaultStation.value?.code && !searchFields.value.stationCode) {
searchFields.value.stationCode = defaultStation.value.code;
}
});
// 当选中的车站第一次有值时,主动获取数据
watch(
() => searchFields.value.stationCode,
(newCode, oldCode) => {
if (oldCode === undefined && !!newCode) {
getTableData();
}
},
{
immediate: true,
},
);
</script>
<template>
<NFlex vertical :size="0" style="height: 100%">
<!-- 查询面板 -->
<NForm style="flex: 0 0 auto; padding: 8px">
<NGrid cols="3" :x-gap="24">
<NFormItemGi span="1" label="车站" label-placement="left">
<NSelect
v-model:value="searchFields.stationCode"
:options="stationSelectOptions"
:render-label="
(option: SelectOption) => {
return [
h(NTag, { type: option.disabled ? 'error' : 'success', size: 'tiny' }, { default: () => (option.disabled ? '离线' : '在线') }),
h('span', {}, { default: () => `${option.label}` }),
];
}
"
:multiple="false"
clearable
/>
</NFormItemGi>
<NFormItemGi span="1" label="时间" label-placement="left">
<NDatePicker v-model:formatted-value="searchFields.createdTime" type="datetimerange" />
</NFormItemGi>
</NGrid>
<!-- 操作按钮 -->
<NGrid :cols="1">
<NGridItem>
<NFlex>
<NButton @click="onClickReset">重置</NButton>
<NButton type="primary" :loading="tableLoading" @click="onClickQuery">查询</NButton>
</NFlex>
</NGridItem>
</NGrid>
</NForm>
<!-- 数据表格工具栏 -->
<NFlex align="center" style="padding: 8px; flex: 0 0 auto">
<div style="font-size: medium">视频平台日志</div>
<NFlex style="margin-left: auto">
<NButton type="primary" :loading="exporting" @click="() => exportTableData()">导出</NButton>
</NFlex>
</NFlex>
<!-- 数据表格 -->
<NDataTable remote :columns="tableColumns" :data="tableData" :pagination="pagination" :loading="tableLoading" :single-line="false" flex-height style="height: 100%; padding: 8px; flex: 1 1 auto" />
</NFlex>
</template>
<style scoped lang="scss"></style>