Files
ndm-web-client/src/pages/vimp-log-page.vue
2025-11-22 01:46:16 +08:00

350 lines
12 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 lang="ts">
const vimpOperationTypeOptions: SelectOption[] = [
{ label: '视频点播', value: 10001 },
{ label: '视频回放', value: 10002 },
{ label: '停止视频回放', value: 10003 },
{ label: '回放暂停', value: 10004 },
{ label: '回放回复', value: 10005 },
{ label: '回放倍速播放', value: 10006 },
{ label: '回放拖动播放', value: 10007 },
{ label: '云台指令', value: 10008 },
{ label: '根据国标码查录像', value: 10009 },
{ label: '下载录像', value: 10010 },
{ label: '停止下载录像', value: 10011 },
{ label: '获取预置位列表', value: 10012 },
{ label: '设置本平台分屏数量', value: 20001 },
{ label: '设置本平台monitor播放的摄像机rtsp流', value: 20002 },
{ label: '停止本平台monitor播放的摄像机rtsp流', value: 20003 },
{ label: '启动非报警时序', value: 30001 },
{ label: '停止非报警时序', value: 30002 },
{ label: '暂停非报警时序', value: 30003 },
{ label: '调用组切', value: 30004 },
{ label: '启动报警时序', value: 40001 },
{ label: '停止报警时序', value: 40002 },
{ label: '分页查询报警', value: 50001 },
{ label: '确认报警', value: 50002 },
{ label: '删除报警', value: 50004 },
];
</script>
<script setup lang="ts">
import type { NdmVimpLogResultVO } from '@/apis/model/biz';
import { exportVimpLogApi, pageVimpLogApi } from '@/apis/request';
import { useStationStore } from '@/stores/station';
import { downloadByData } from '@/utils/download';
import { useMutation } from '@tanstack/vue-query';
import dayjs from 'dayjs';
import destr from 'destr';
import {
NButton,
NDataTable,
NDatePicker,
NForm,
NFormItemGi,
NGrid,
NGridItem,
NPopover,
NScrollbar,
NSelect,
NSpace,
NTag,
type DataTableColumns,
type DataTableRowData,
type PaginationProps,
type SelectOption,
} from 'naive-ui';
import { storeToRefs } from 'pinia';
import { computed, h, onMounted, reactive, ref, watch, watchEffect } from 'vue';
const stationStore = useStationStore();
const { stationList, onlineStationList } = storeToRefs(stationStore);
const stationSelectOptions = computed(() => {
return stationList.value.map<SelectOption>((station) => ({
label: station.name,
value: station.code,
disabled: !station.online,
}));
});
const searchFields = reactive({
stationCode: undefined as string | undefined,
logType_in: [] as number[],
createdTime: [dayjs().startOf('date').subtract(1, 'week').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('date').format('YYYY-MM-DD HH:mm:ss')] as [string, string],
});
const resetSearchFields = () => {
searchFields.stationCode = stationList.value.find((station) => station.online)?.code;
searchFields.logType_in = [];
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 getExtraFields = () => {
const logType_in = searchFields.logType_in.length > 0 ? [...searchFields.logType_in] : undefined;
const createdTime_precisest = searchFields.createdTime[0];
const createdTime_preciseed = searchFields.createdTime[1];
return {
logType_in,
createdTime_precisest,
createdTime_preciseed,
};
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const onDateChange = (value: [number, number] | null) => {
if (!value) {
return;
}
const [start, end] = value;
const diffDays = dayjs(end).diff(dayjs(start), 'day');
if (diffDays > 7) {
// 如果超过7天自动调整结束时间
const adjustedEnd = dayjs(start).add(7, 'day').valueOf();
searchFields.createdTime = [dayjs(start).format('YYYY-MM-DD HH:mm:ss'), dayjs(adjustedEnd).format('YYYY-MM-DD HH:mm:ss')];
window.$message.warning('时间范围不能超过7天已自动调整');
} else {
searchFields.createdTime = [dayjs(start).format('YYYY-MM-DD HH:mm:ss'), dayjs(end).format('YYYY-MM-DD HH:mm:ss')];
}
};
const searchFieldsChanged = ref(false);
watch(searchFields, () => {
searchFieldsChanged.value = true;
});
const tableColumns: DataTableColumns<NdmVimpLogResultVO> = [
{ title: '时间', key: 'createdTime' },
{ title: '操作类型', key: 'description' },
{ title: '请求IP', key: 'requestIp' },
{ title: '耗时(ms)', key: 'consumedTime' },
{ title: '被调用设备', key: 'targetCode' },
{
title: '操作参数',
key: 'params',
width: 100,
render(rowData) {
const result = JSON.stringify(destr(rowData.params), null, 2);
// return h('pre', {}, { default: () => result });
return h(
NPopover,
{ trigger: 'click' },
{
trigger: () => h(NButton, { size: 'tiny', text: true, type: 'primary' }, { default: () => '查看' }),
default: () =>
h(
NScrollbar,
{ style: { maxHeight: '40vh' } },
{
default: () => h('pre', {}, { default: () => result }),
},
),
},
);
},
},
{
title: '操作结果',
key: 'result',
width: 100,
render: (rowData) => {
const result = JSON.stringify(destr(rowData.result), null, 2);
return h(
NPopover,
{ trigger: 'click' },
{
trigger: () => h(NButton, { size: 'tiny', text: true, type: 'primary' }, { default: () => '查看' }),
default: () =>
h(
NScrollbar,
{ style: { maxHeight: '40vh' } },
{
default: () => h('pre', {}, { default: () => result }),
},
),
},
);
},
},
];
const tableData = ref<DataTableRowData[]>([]);
const tablePagination = reactive<PaginationProps>({
showSizePicker: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 20, 50, 80, 100],
itemCount: 0,
prefix: ({ itemCount }) => {
return h('div', {}, { default: () => `${itemCount}` });
},
onUpdatePage: (page: number) => {
tablePagination.page = page;
getVimpLogList();
},
onUpdatePageSize: (pageSize: number) => {
tablePagination.pageSize = pageSize;
tablePagination.page = 1;
getVimpLogList();
},
});
const { mutate: getVimpLogList, isPending: tableLoading } = useMutation({
mutationFn: async () => {
if (!searchFields.stationCode) throw Error('请选择车站');
const res = await pageVimpLogApi(
{
model: {},
extra: getExtraFields(),
current: tablePagination.page ?? 1,
size: tablePagination.pageSize ?? 10,
order: 'descending',
sort: 'id',
},
{
stationCode: searchFields.stationCode,
},
);
return res;
},
onSuccess: (res) => {
const { records, size, total } = res;
tablePagination.pageSize = parseInt(size);
tablePagination.itemCount = parseInt(total);
tableData.value = records;
},
onError: (error) => {
console.error(error);
window.$message.error(error.message);
},
});
const onClickReset = () => {
resetSearchFields();
tablePagination.page = 1;
tablePagination.pageSize = 10;
tablePagination.itemCount = 0;
getVimpLogList();
};
const onClickQuery = () => {
if (searchFieldsChanged.value) {
tablePagination.page = 1;
tablePagination.pageSize = 10;
searchFieldsChanged.value = false;
}
getVimpLogList();
};
const { mutate: exportTableData, isPending: exporting } = useMutation({
mutationFn: async () => {
if (!searchFields.stationCode) throw Error('请选择车站');
const data = await exportVimpLogApi(
{
model: {},
extra: getExtraFields(),
current: tablePagination.page ?? 1,
size: tablePagination.pageSize ?? 10,
order: 'descending',
sort: 'id',
},
{
stationCode: searchFields.stationCode,
},
);
return data;
},
onSuccess: (data) => {
const time = dayjs().format('YYYY-MM-DD_HH-mm-ss');
downloadByData(data, `视频操作日志_${time}.xlsx`);
},
onError: (error) => {
console.error(error);
window.$message.error(error.message);
},
});
// 进入页面时选择首个在线的车站
onMounted(() => {
// if (onlineStationList.value.length > 0) {
// searchFields.stationCode = onlineStationList.value.at(0)?.code;
// getVimpLogList();
// } else {
// const watchHandle = watch(onlineStationList, (onlineStations) => {
// searchFields.stationCode = onlineStations.at(0)?.code;
// watchHandle.stop();
// getVimpLogList();
// });
// }
});
// 进入页面时选择首个在线的车站
// 当页面刷新时车站列表还没有加载完成defaultStation不存在if条件为false
// 等待车站列表加载完成后defaultStation会更新if条件为true会更新选择的车站并加载日志记录。
// 当从其他页面跳转过来时车站列表已经存在defaultStation存在if条件为true会更新选择的车站并加载日志记录。
// 由于if条件是and逻辑所以即使defaultStation因车站状态变化而更新由于已经选择了车站所以if条件为false。
const defaultStation = computed(() => onlineStationList.value.at(0));
watchEffect(() => {
if (defaultStation.value?.code && !searchFields.stationCode) {
searchFields.stationCode = defaultStation.value.code;
getVimpLogList();
}
});
</script>
<template>
<!-- 容器上下布局表格自适应剩余高度 -->
<div style="height: 100%; display: flex; flex-direction: column">
<!-- 查询面板 -->
<div style="flex: 0 0 auto; padding: 8px">
<NForm>
<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">
<NSelect v-model:value="searchFields.logType_in" :options="vimpOperationTypeOptions" multiple clearable />
</NFormItemGi>
<NFormItemGi :span="1" label="时间" label-placement="left">
<NDatePicker v-model:formatted-value="searchFields.createdTime" type="datetimerange" @update:value="undefined" />
</NFormItemGi>
</NGrid>
<!-- 按钮 -->
<NGrid :cols="1">
<NGridItem>
<NSpace>
<NButton @click="onClickReset">重置</NButton>
<NButton type="primary" :loading="tableLoading" @click="onClickQuery">查询</NButton>
</NSpace>
</NGridItem>
</NGrid>
</NForm>
</div>
<!-- 工具栏:横向、右对齐按钮) -->
<div style="flex: 0 0 auto; display: flex; align-items: center; padding: 8px">
<div style="font-size: medium">视频平台日志</div>
<NSpace style="margin-left: auto">
<NButton type="primary" :loading="exporting" @click="() => exportTableData()">导出</NButton>
</NSpace>
</div>
<!-- 表格区域:填满剩余空间 -->
<div style="flex: 1 1 auto; min-height: 0; padding: 8px">
<NDataTable remote :columns="tableColumns" :data="tableData" :pagination="tablePagination" :loading="tableLoading" :single-line="false" flex-height style="height: 100%" />
</div>
</div>
</template>
<style scoped lang="scss"></style>