|
|
@@ -1,5 +1,269 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import {
|
|
|
+ NButton,
|
|
|
+ NGridItem,
|
|
|
+ NInput,
|
|
|
+ NSpace,
|
|
|
+ NStatistic,
|
|
|
+ NNumberAnimation,
|
|
|
+ NGrid,
|
|
|
+ NSelect,
|
|
|
+ NDatePicker,
|
|
|
+ NBadge,
|
|
|
+ NRadioGroup,
|
|
|
+ NRadioButton,
|
|
|
+ PaginationProps,
|
|
|
+ NPagination,
|
|
|
+ NScrollbar,
|
|
|
+ NForm,
|
|
|
+ NFormItemGi,
|
|
|
+} from "naive-ui";
|
|
|
+import { useAlarmStore, toRefsAlarmStore } from "@/stores/modules/alarm";
|
|
|
+import GridData from "./components/gridData.vue";
|
|
|
+import TableData from "./components/tableData.vue";
|
|
|
+
|
|
|
+const { API_DEVICE_GET, API_RECORD_GET } = useRequest();
|
|
|
+
|
|
|
+// 视图状态
|
|
|
+const viewMode = ref("grid"); // 'grid' | 'table'
|
|
|
+
|
|
|
+const { getFileUrl } = useAlarmStore();
|
|
|
+const { alarms, alarmCount } = toRefsAlarmStore();
|
|
|
+
|
|
|
+const viewModeOptions = [
|
|
|
+ {
|
|
|
+ label: "网格视图",
|
|
|
+ value: "grid",
|
|
|
+ icon: " i-lucide-layout-grid",
|
|
|
+ },
|
|
|
+ { label: "表格视图", value: "table", icon: " i-lucide-table" },
|
|
|
+];
|
|
|
+
|
|
|
+export interface AlertItem {
|
|
|
+ ID: number;
|
|
|
+ DeviceId: number;
|
|
|
+ Alias: string;
|
|
|
+ EdgeIp: string;
|
|
|
+ PersonCount: number;
|
|
|
+ PersonThreshold: number;
|
|
|
+ EventTime: number;
|
|
|
+ Msg: string;
|
|
|
+}
|
|
|
+
|
|
|
+export interface formInfo {
|
|
|
+ start_time: number | null; // 事件开始时间戳:秒级,筛选事件时间≥该值,默认0(不限制开始时间)
|
|
|
+ end_time: number | null; // 事件结束时间戳:秒级,筛选事件时间≤该值,默认0(不限制结束时间)
|
|
|
+ min_confidence?: number | null; // 最小置信度:筛选置信度≥该值,默认0.0(不限制置信度)
|
|
|
+ page: number; // 页码:从1开始,默认1(第一页)
|
|
|
+ page_size: number; // 每页条数:默认20条/页
|
|
|
+ device_id: number; // 设备id
|
|
|
+}
|
|
|
+
|
|
|
+const alertData = ref<AlertItem[]>([]);
|
|
|
+const prevUserListTotal = ref(0);
|
|
|
+
|
|
|
+const isRequestLoading = ref(true);
|
|
|
+const deviceOptions = ref([]);
|
|
|
+async function getDeviceList() {
|
|
|
+ const res = await API_DEVICE_GET();
|
|
|
+ deviceOptions.value = (res as any[]).map((item) => ({
|
|
|
+ label: item.Alias,
|
|
|
+ value: item.Id,
|
|
|
+ }));
|
|
|
+}
|
|
|
+async function getDataList(form = {}) {
|
|
|
+ const res: any = await API_RECORD_GET(searchForm);
|
|
|
+ alertData.value = res.data as AlertItem[];
|
|
|
+ pagination.itemCount = res.total;
|
|
|
+ isRequestLoading.value = false;
|
|
|
+}
|
|
|
+
|
|
|
+const nDatePickerV = ref<[number, number] | null>(null);
|
|
|
+const searchForm = reactive<formInfo>({
|
|
|
+ device_id: null,
|
|
|
+ start_time: null,
|
|
|
+ end_time: null,
|
|
|
+ page: 1,
|
|
|
+ page_size: 20,
|
|
|
+});
|
|
|
+const pagination = reactive<PaginationProps>({
|
|
|
+ page: 1,
|
|
|
+ pageSize: 20,
|
|
|
+ showSizePicker: true,
|
|
|
+ pageSizes: [10, 20, 50, 100],
|
|
|
+ itemCount: 0,
|
|
|
+ showQuickJumper: true,
|
|
|
+ showQuickJumpDropdown: true,
|
|
|
+ onUpdatePage: (page: number) => {
|
|
|
+ pagination.page = page;
|
|
|
+ searchForm.page = page;
|
|
|
+ getDataList();
|
|
|
+ },
|
|
|
+ onUpdatePageSize: (pageSize: number) => {
|
|
|
+ pagination.pageSize = pageSize;
|
|
|
+ pagination.page = 1;
|
|
|
+ searchForm.page_size = pageSize;
|
|
|
+ getDataList();
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+const paginationPrefix: PaginationProps["prefix"] = (info) => {
|
|
|
+ const { itemCount } = info;
|
|
|
+ if (!itemCount) return null;
|
|
|
+ return h("div", null, [
|
|
|
+ h("span", "总\u00A0"),
|
|
|
+ h(NNumberAnimation, {
|
|
|
+ from: prevUserListTotal.value,
|
|
|
+ to: itemCount,
|
|
|
+ onFinish: () => {
|
|
|
+ prevUserListTotal.value = itemCount;
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ h("span", "\u00A0条"),
|
|
|
+ ]);
|
|
|
+};
|
|
|
+
|
|
|
+watchThrottled(
|
|
|
+ alarms,
|
|
|
+ () => {
|
|
|
+ // console.log(v) // TODO
|
|
|
+ getDataList();
|
|
|
+ },
|
|
|
+ { throttle: 3000, deep: true },
|
|
|
+);
|
|
|
+
|
|
|
+const gridDataRef = useTemplateRef<typeof GridData>("gridDataRef");
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ getDeviceList();
|
|
|
+ getDataList();
|
|
|
+});
|
|
|
+
|
|
|
+provide("getDataList", getDataList);
|
|
|
+</script>
|
|
|
+
|
|
|
<template>
|
|
|
- <div>12</div>
|
|
|
+ <div class="h-100% flex flex-col bg-bl p-4 text-slate-200">
|
|
|
+ <!-- 过滤与搜索工具栏 -->
|
|
|
+ <section
|
|
|
+ class="flex flex-wrap items-center justify-between gap-4 mb-2 bg-[#1d293d] p-4 rounded border border-white/5 backdrop-blur-md"
|
|
|
+ >
|
|
|
+ <NForm
|
|
|
+ ref="formRef"
|
|
|
+ class="w-full"
|
|
|
+ :model="searchForm"
|
|
|
+ label-placement="left"
|
|
|
+ label-width="auto"
|
|
|
+ >
|
|
|
+ <n-grid
|
|
|
+ :x-gap="12"
|
|
|
+ :y-gap="8"
|
|
|
+ cols="3"
|
|
|
+ item-responsive
|
|
|
+ responsive="screen"
|
|
|
+ class="items-center"
|
|
|
+ >
|
|
|
+ <n-form-item-gi
|
|
|
+ span="1"
|
|
|
+ label="设备列表"
|
|
|
+ path="status"
|
|
|
+ :show-feedback="false"
|
|
|
+ >
|
|
|
+ <NSelect
|
|
|
+ v-model:value="searchForm.device_id"
|
|
|
+ :options="deviceOptions"
|
|
|
+ style="min-width: 88px"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </n-form-item-gi>
|
|
|
+ <n-form-item-gi
|
|
|
+ span="1"
|
|
|
+ label="时间范围"
|
|
|
+ path="status"
|
|
|
+ :show-feedback="false"
|
|
|
+ >
|
|
|
+ <NDatePicker
|
|
|
+ ref="nDatePickerRef"
|
|
|
+ type="datetimerange"
|
|
|
+ clearable
|
|
|
+ v-model:value="nDatePickerV"
|
|
|
+ @update:value="
|
|
|
+ (v: number[]) => {
|
|
|
+ searchForm.start_time = v ? v[0] : null;
|
|
|
+ searchForm.end_time = v ? v[1] : null;
|
|
|
+ }
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </n-form-item-gi>
|
|
|
+ <n-form-item-gi
|
|
|
+ span="1"
|
|
|
+ class="flex items-center justify-end"
|
|
|
+ :show-feedback="false"
|
|
|
+ >
|
|
|
+ <n-button type="primary" size="medium" @click="getDataList">
|
|
|
+ <template #icon>
|
|
|
+ <span class="i-lucide-search text-white" />
|
|
|
+ </template>
|
|
|
+ 搜索
|
|
|
+ </n-button>
|
|
|
+ <div class="h-6 w-px bg-blue/10 mx-2" />
|
|
|
+ <!-- 布局切换按钮 -->
|
|
|
+ <n-radio-group
|
|
|
+ v-model:value="viewMode"
|
|
|
+ name="radiobuttongroup2"
|
|
|
+ size="medium"
|
|
|
+ >
|
|
|
+ <n-radio-button
|
|
|
+ v-for="item in viewModeOptions"
|
|
|
+ :key="item.value"
|
|
|
+ :value="item.value"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="h-full w-full flex items-center justify-center absolute left-0"
|
|
|
+ >
|
|
|
+ <span :class="item.icon"></span>
|
|
|
+ </div>
|
|
|
+ </n-radio-button>
|
|
|
+ </n-radio-group>
|
|
|
+ </n-form-item-gi>
|
|
|
+ </n-grid>
|
|
|
+ </NForm>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 主体列表区域 -->
|
|
|
+ <main
|
|
|
+ :class="['flex-1 mb-2', viewMode === 'grid' ? ' overflow-y-auto' : '']"
|
|
|
+ id="image-scroll-container"
|
|
|
+ >
|
|
|
+ <!-- 网格视图 -->
|
|
|
+ <GridData
|
|
|
+ v-if="viewMode === 'grid'"
|
|
|
+ ref="gridDataRef"
|
|
|
+ :alertData="alertData"
|
|
|
+ :loading="isRequestLoading"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 表格视图 -->
|
|
|
+ <template v-else>
|
|
|
+ <TableData :alertData="alertData" :loading="isRequestLoading" />
|
|
|
+ </template>
|
|
|
+ <!-- <div v-if="alertData.length === 0" class="py-24 text-center">
|
|
|
+ <n-empty description="未检索到相关告警信息" />
|
|
|
+ </div> -->
|
|
|
+ </main>
|
|
|
+ <!-- 页脚 -->
|
|
|
+ <div class="flex justify-end">
|
|
|
+ <n-pagination
|
|
|
+ v-bind="pagination"
|
|
|
+ :prefix="paginationPrefix"
|
|
|
+ size="medium"
|
|
|
+ show-quick-jumper
|
|
|
+ show-size-picker
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
-<script setup></script>
|
|
|
+<style scoped>
|
|
|
+/* Naive UI 深色主题微调 */
|
|
|
+</style>
|