浏览代码

feat: 增加定时任务日志,项目详情中摄像头预览绑定摄像头代码对接新的流媒体版本

yanglzh 3 月之前
父节点
当前提交
6a50b079ec
共有 4 个文件被更改,包括 329 次插入24 次删除
  1. 6 0
      src/api/system/index.ts
  2. 23 0
      src/utils/loading-util.ts
  3. 21 24
      src/views/iot/projects/detail/video.vue
  4. 279 0
      src/views/system/monitor/task-logs/index.vue

+ 6 - 0
src/api/system/index.ts

@@ -160,6 +160,12 @@ export default {
     stop: (id: number) => put('/system/job/stop', { id }),
     getFunList: () => get('system/job/fun_list'),
   },
+  task_log: {
+    getList: (params: object) => get('/system/job_log/list', params),
+    detail: (id: number) => get('/system/job_log/get', { id }),
+    del: (ids: number[]) => del('/system/job_log/delete', { ids }),
+    export: (params: object) => file('/system/job_log/export', params),
+  },
   city: {
     getList: (params: object) => get('/common/city/tree', params),
     add: (data: object) => post('/common/city/add', data),

+ 23 - 0
src/utils/loading-util.ts

@@ -0,0 +1,23 @@
+import { Ref, ref } from 'vue'
+
+// eslint-disable-next-line no-unused-vars
+export function useLoading<T extends (...param: Parameters<T>) => Promise<void>>(
+  // eslint-disable-next-line no-unused-vars
+  inner: T
+): {
+  loading: Ref<boolean>
+  // eslint-disable-next-line no-unused-vars
+  doLoading: (...param: Parameters<T>) => Promise<void>
+} {
+  const loading = ref(false)
+
+  return {
+    loading,
+    doLoading: async (...param: Parameters<T>) => {
+      loading.value = true
+      return inner(...param).finally(() => {
+        loading.value = false
+      })
+    },
+  }
+}

+ 21 - 24
src/views/iot/projects/detail/video.vue

@@ -2,16 +2,15 @@
   <div class="tab-content h-full">
     <div class="subtitle"><span></span> <el-button type="primary" v-auth="'add'" size="small" @click="addDevice()">添加监控</el-button></div>
     <el-table :data="tableData" style="width: 100%" v-loading="loading" max-height="calc(100vh - 280px)">
-      <el-table-column prop="DeviceName" label="设备名称" min-width="130" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="DeviceID" label="设备ID" min-width="210" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="Name" label="通道名称" min-width="140" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="Model" label="型号" width="100" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="Manufacturer" label="厂商" width="100" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="LiveStatus" label="状态" width="100" align="center" :formatter="formatLiveStatus" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="NetAddr" label="网络地址" width="180" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="LastKeepaliveAt" label="最后心跳时间" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="RegisterTime" label="注册时间" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="UpdateTime" label="更新时间" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="name" label="设备名称" min-width="130" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="deviceId" label="设备ID" min-width="210" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="name" label="通道名称" min-width="140" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="model" label="型号" width="100" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="manufacturer" label="厂商" width="100" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="liveStatus" label="状态" width="100" align="center" :formatter="formatLiveStatus" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="keepAliveTime" label="最后心跳时间" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="registerTime" label="注册时间" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="updateAt" label="更新时间" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
       <el-table-column label="操作" width="120" align="center" fixed="right">
         <template #default="scope">
           <el-button size="small" text type="primary" v-auth="'detail'" @click="view(scope.row)">查看监控</el-button>
@@ -85,7 +84,7 @@ function getSourceList() {
 
     ids.forEach((id: string) => {
       const [deviceID, channelID] = id.split("-");
-      const channel = channels.value.find((channel: any) => channel.ID === deviceID && channel.DeviceID === channelID);
+      const channel = channels.value.find((channel: any) => channel.deviceId === deviceID && channel.channelId === channelID);
       if (channel) {
         options.value.find((item: any) => item.value === id).disabled = true;
         tableDataList.push(channel);
@@ -98,22 +97,20 @@ function getSourceList() {
 
 function getOptions() {
   // 获取流媒体设备列表,拼接成通道列表 设备ID-通道ID
-  axios.get(getMediaOrigin("/gb28181/api/list?format=json")).then((res: any) => {
-    // console.log(res.data.data);
+  axios.get(getMediaOrigin("/gb28181/api/list?page=1&count=1000&format=json")).then((res: any) => {
     const optionsList: any[] = [];
     const channelsList: any[] = [];
     res.data.data.forEach((item: any) => {
-      item.Channels.forEach((channel: any) => {
-        optionsList.push({ label: item.Name + " - " + channel.Name, value: item.ID + "-" + channel.DeviceID });
+      item.channels.forEach((channel: any) => {
+        optionsList.push({ label: item.name + " - " + channel.name, value: item.deviceId + "-" + channel.channelId });
         channelsList.push({
           ...channel,
-          DeviceName: item.Name,
-          NetAddr: item.NetAddr,
-          RegisterTime: item.RegisterTime,
-          UpdateTime: item.UpdateTime,
-          LastKeepaliveAt: item.LastKeepaliveAt,
-          ID: item.ID,
-          DeviceID: channel.DeviceID,
+          name: item.name,
+          netAddr: item.netAddr,
+          registerTime: item.registerTime,
+          updateAt: item.updateAt,
+          keepAliveTime: item.keepAliveTime,
+          deviceId: channel.deviceId,
         });
       });
     });
@@ -142,7 +139,7 @@ function addDevice() {
 }
 
 function view(row: any) {
-  previewUrl.value = import.meta.env.VITE_SERVER_ORIGIN + "/plugin/media/index.html?ID=" + row.ID + "&DeviceID=" + row.DeviceID + "#/screen-preview";
+  previewUrl.value = import.meta.env.VITE_SERVER_ORIGIN + "/plugin/media/index.html?ID=" + row.channelId + "&DeviceID=" + row.deviceId + "#/0/screen-preview";
   isShowPreviewDialog.value = true;
 }
 
@@ -161,7 +158,7 @@ function onDel(row: any) {
       .unbindResources({
         projectsCode,
         resourcesTypes,
-        resourcesKey: row.ID + "-" + row.DeviceID,
+        resourcesKey: row.ID + "-" + row.deviceId,
       })
       .then(() => {
         getSourceList();

+ 279 - 0
src/views/system/monitor/task-logs/index.vue

@@ -0,0 +1,279 @@
+<script setup lang="ts">
+import api from "/@/api/system";
+import { onMounted, reactive, ref } from "vue";
+import { Exception } from "sass";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { useLoading } from "/@/utils/loading-util";
+import downloadFile from "/@/utils/download";
+// eslint-disable-next-line no-unused-vars
+enum StatusEnum {
+  // eslint-disable-next-line no-unused-vars
+  SUCCESS = 0,
+  // eslint-disable-next-line no-unused-vars
+  FAILED = 1,
+}
+type TaskLogSummary = {
+  id: number;
+  jobName: string;
+  invokeTarget: string;
+  cronExpression: string;
+  startTime: string; //开始时间
+  endTime?: string; //结束时间(仅success/failed拥有)
+  createdAt: string; //创建时间
+  status: StatusEnum;
+};
+//数据搜索部分开始
+const searchParam = reactive<{
+  pageNum: number;
+  pageSize: number;
+  jobName?: string;
+  dateRange?: string[];
+  status?: StatusEnum;
+}>({
+  pageNum: 1,
+  pageSize: 10,
+});
+const total = ref<number>(0);
+const data = ref<Array<TaskLogSummary>>([]);
+const { loading, doLoading: doListLoad } = useLoading(async () => {
+  const res: {
+    list: TaskLogSummary[];
+    total: number;
+  } = await api.task_log.getList(searchParam).catch((ex: Exception) => {
+    // eslint-disable-next-line no-console
+    console.log(ex);
+    return {
+      list: [],
+      total: 0,
+    };
+  });
+  total.value = res.total;
+  data.value = res.list;
+});
+const reset = () => {
+  searchParam.pageNum = 1;
+  searchParam.pageSize = 10;
+  searchParam.jobName = undefined;
+  searchParam.dateRange = undefined;
+  searchParam.status = undefined;
+  doListLoad();
+};
+onMounted(doListLoad);
+//数据搜索部分结束
+//数据选择删除部分开始
+const ids = ref<number[]>([]);
+const onDeleteItemSelected = (row: TaskLogSummary[]) => {
+  ids.value = row.map((item) => item.id);
+};
+const del = async () => {
+  if (ids.value.length === 0) {
+    ElMessage.error("请选择要删除的数据");
+    return;
+  }
+  const confirm = await ElMessageBox.confirm("您确定要删除所选数据吗?", "提示", {
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning",
+  });
+  if (confirm != "confirm") {
+    return;
+  }
+  const res = await api.task_log
+    .del(ids.value)
+    .then(() => true)
+    .catch((ex: Exception) => {
+      // eslint-disable-next-line no-console
+      console.log(ex);
+      return false;
+    });
+  if (!res) {
+    ElMessage.error("删除失败");
+  }
+  ElMessage.success("删除成功");
+  await doListLoad();
+  return;
+};
+const delSingle = async (id: number) => {
+  const confirm = await ElMessageBox.confirm("您确定要删除所选数据吗?", "提示", {
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning",
+  });
+  if (confirm != "confirm") {
+    return;
+  }
+  const res = await api.task_log
+    .del([id])
+    .then(() => true)
+    .catch(() => {
+      return false;
+    });
+  if (!res) {
+    ElMessage.error("删除失败");
+  }
+  ElMessage.success("删除成功");
+  await doListLoad();
+  return;
+};
+//数据选择删除部分结束
+//导出日志部分开始
+const { loading: exportLoading, doLoading: doExport } = useLoading(async () => {
+  const res = await api.task_log.export(searchParam).catch((ex: Exception) => {
+    // eslint-disable-next-line no-console
+    console.log(ex);
+    return undefined;
+  });
+  if (!res) {
+    ElMessage.error("导出失败");
+    return;
+  }
+  ElMessage.success("导出成功");
+  downloadFile(res);
+});
+//导出日志部分结束
+//查看日志详情部分开始
+const detailForm = ref<any>({
+  id: 0,
+  jobName: "",
+  invokeTarget: "",
+  cronExpression: "",
+  startTime: "",
+  endTime: "",
+  createdAt: "",
+  status: 0,
+  exceptionInfo: "",
+  jobMessage: "",
+});
+const detailDialogOpen = ref(false);
+const detailTabsNumber = ref<"0" | "1" | "2">("0");
+const { loading: detailLoading, doLoading: doDetailLoad } = useLoading(async (id: number) => {
+  detailForm.value = { id };
+  const res = await api.task_log.detail(id).catch((ex: Exception) => {
+    // eslint-disable-next-line no-console
+    console.log(ex);
+    return undefined;
+  });
+  if (!res) {
+    ElMessage.error("获取失败");
+    return;
+  }
+  detailForm.value = res;
+  detailDialogOpen.value = true;
+});
+</script>
+
+<template>
+  <el-card shadow="nover" class="page">
+    <el-form :model="searchParam" inline>
+      <el-form-item label="" prop="jobName">
+        <el-input style="width: 150px" v-model="searchParam.jobName" placeholder="任务名称"></el-input>
+      </el-form-item>
+      <el-form-item label="" prop="dateRange">
+        <el-date-picker v-model="searchParam.dateRange" style="width: 220px" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="开始时间" end-placeholder="结束时间"></el-date-picker>
+      </el-form-item>
+      <el-form-item label="" prop="status">
+        <el-select style="width: 125px" v-model="searchParam.status" placeholder="请选择">
+          <el-option label="全部" :value="undefined"></el-option>
+          <el-option label="成功" :value="StatusEnum.SUCCESS"></el-option>
+          <el-option label="失败" :value="StatusEnum.FAILED"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" class="ml10" @click="doListLoad">
+          <el-icon>
+            <ele-Search />
+          </el-icon>
+          查询
+        </el-button>
+        <el-button @click="reset">
+          <el-icon>
+            <ele-Refresh />
+          </el-icon>
+          重置
+        </el-button>
+
+        <el-button type="info" @click="del" v-auth="'del'">
+          <el-icon>
+            <ele-Delete />
+          </el-icon>
+          删除日志
+        </el-button>
+
+        <el-button type="primary" @click="doExport" v-loading="exportLoading" v-auth="'download'">
+          <el-icon>
+            <ele-Download />
+          </el-icon>
+          导出日志
+        </el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-table :data="data" style="width: 100%" v-loading="loading" @selection-change="onDeleteItemSelected">
+      <el-table-column type="selection" width="50" align="center"></el-table-column>
+      <el-table-column label="ID" prop="id" width="90" align="center" v-col="'jobId'"></el-table-column>
+      <el-table-column label="任务名称" prop="jobName" align="center" v-col="'jobName'"></el-table-column>
+      <el-table-column label="功能名称" prop="invokeTarget" align="center" v-col="'invokeTaget'"></el-table-column>
+      <el-table-column label="表达式" prop="cronExpression" align="center" v-col="'cronExpression'"></el-table-column>
+      <el-table-column label="开始时间" prop="startTime" align="center" v-col="'startTime'"></el-table-column>
+      <el-table-column label="结束时间" prop="endTime" align="center" v-col="'endTime'"></el-table-column>
+      <el-table-column label="创建时间" prop="createdAt" align="center" v-col="'createdAt'"></el-table-column>
+      <el-table-column label="状态" prop="status" align="center" v-col="'status'">
+        <template #default="scope">
+          <el-tag :type="scope.row.status === StatusEnum.SUCCESS ? 'success' : scope.row.status === StatusEnum.FAILED ? 'danger' : 'info'">
+            {{ scope.row.status === StatusEnum.SUCCESS ? "成功" : scope.row.status === StatusEnum.FAILED ? "失败" : "未知" }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="180">
+        <template #default="scope">
+          <el-button type="text" size="small" @click="doDetailLoad(scope.row.id)" v-loading="detailLoading && detailForm.id === scope.row.id">
+            <el-icon>
+              <ele-Eye />
+            </el-icon>
+            查看
+          </el-button>
+          <el-button type="text" size="small" @click="delSingle(scope.row.id)" v-auth="'del'">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination v-show="total > 0" :total="total" v-model:page="searchParam.pageNum" v-model:limit="searchParam.pageSize" @pagination="doListLoad" />
+
+    <el-dialog title="日志详情" v-model="detailDialogOpen" width="80%" destroy-on-close>
+      <el-tabs v-model="detailTabsNumber" type="border-card">
+        <el-tab-pane label="基本信息" name="0">
+          <el-form :model="detailForm" label-width="100px">
+            <el-form-item label="任务名称" prop="jobName">
+              <el-input style="width: 150px" v-model="detailForm.jobName" disabled></el-input>
+            </el-form-item>
+            <el-form-item label="功能名称" prop="invokeTarget">
+              <el-input style="width: 150px" v-model="detailForm.invokeTarget" disabled></el-input>
+            </el-form-item>
+            <el-form-item label="表达式" prop="cronExpression">
+              <el-input style="width: 150px" v-model="detailForm.cronExpression" disabled></el-input>
+            </el-form-item>
+            <el-form-item label="开始时间" prop="startTime">
+              <el-input style="width: 150px" v-model="detailForm.startTime" disabled></el-input>
+            </el-form-item>
+            <el-form-item label="结束时间" prop="endTime">
+              <el-input style="width: 150px" v-model="detailForm.endTime" disabled></el-input>
+            </el-form-item>
+            <el-form-item label="状态" prop="status">
+              <el-tag :type="detailForm?.status === StatusEnum.SUCCESS ? 'success' : detailForm?.status === StatusEnum.FAILED ? 'danger' : 'info'">
+                {{ detailForm?.status === StatusEnum.SUCCESS ? "成功" : detailForm?.status === StatusEnum.FAILED ? "失败" : "未知" }}
+              </el-tag>
+            </el-form-item>
+          </el-form>
+        </el-tab-pane>
+        <el-tab-pane label="结果" name="1">
+          {{ detailForm.jobMessage }}
+        </el-tab-pane>
+        <el-tab-pane label="失败原因" name="2">
+          {{ detailForm.exceptionInfo }}
+        </el-tab-pane>
+      </el-tabs>
+    </el-dialog>
+  </el-card>
+</template>
+
+<style scoped lang="scss"></style>