Commit 92ecdb7d authored by 胡占生's avatar 胡占生 🇨🇳
Browse files

Merge branch 'developer' of http://git.censoft.com.cn/ai-yunshou/ai-yunshou-vue into developer

parents 951203a4 4c844266
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
    "element-plus": "^2.7.6",
    "file-saver": "2.0.5",
    "fuse.js": "6.6.2",
    "html2canvas": "^1.4.1",
    "js-cookie": "3.0.5",
    "jsencrypt": "3.3.2",
    "moment": "^2.30.1",
+354 −74
Original line number Diff line number Diff line
<template>
  <el-dialog v-model="algorithmManage.visible.value" width="60%" append-to-body>
  <el-dialog v-model="algorithmManage.visible.value" width="70%" append-to-body>
    <template v-slot:header>
      <div
        class="cleartitle"
@@ -9,7 +9,7 @@
        <span>{{ title }}</span>
      </div>
    </template>
    <el-row>
    <el-row class="row-box">
      <el-col :span="12">
        <el-row :gutter="10" class="mb8" style="justify-content: space-between">
          <el-col :span="1.5">
@@ -32,9 +32,31 @@
            :model="unref(algorithmManage.form)"
            :rules="rules"
            label-position="left"
            label-width="100px"
            label-width="110px"
            class="demo-form-inline"
          >
            <el-form-item
              label="区域管理"
              prop="deviceId"
              v-if="unref(device.list).length"
            >
              <el-cascader
                style="width: 100%"
                v-model="unref(algorithmManage.form).regionalId"
                :options="areaManger.list.value"
                :props="{
                  children: 'children',
                  value: 'id',
                  checkStrictly: true,
                }"
                placeholder="请选择上级区域"
                clearable
                @change="handleChange"
              />
            </el-form-item>
            <el-form-item label="点位名称" prop="locationName">
              <el-input v-model="unref(algorithmManage.form).locationName" />
            </el-form-item>
            <el-form-item
              label="摄像头名称"
              prop="deviceId"
@@ -54,7 +76,7 @@
              </el-select>
            </el-form-item>
            <!-- RTSP地址 -->
            <el-form-item label="RTSP地址" prop="vedioUrl">
            <el-form-item label="RTSP地址" prop="videoUrl">
              <el-input
                v-model="unref(algorithmManage.form).videoUrl"
                readonly
@@ -70,10 +92,14 @@
              />
            </el-form-item>
            <el-form-item label="重新拉流时间" prop="reStreamingTime">
              <el-date-picker
                v-model="unref(algorithmManage.form).reStreamingTime"
                type="datetime"
              />
              <el-input v-model="unref(algorithmManage.form).reStreamingTime"
                ><template #suffix>/秒</template></el-input
              >
            </el-form-item>
            <el-form-item label="拉流间隔时间" prop="inferenceInterval">
              <el-input v-model="unref(algorithmManage.form).inferenceInterval"
                ><template #suffix>/秒</template></el-input
              >
            </el-form-item>
          </el-form>
        </div>
@@ -84,6 +110,7 @@
              unref(algorithmManage.form).aiRegionalLocationList ||
              aiAlgorithm.list.value
            "
            height="200px"
          >
            <el-table-column
              label="算法名称"
@@ -127,7 +154,7 @@
          </el-table>
        </div>
      </el-col>
      <el-col :span="11" :offset="1" class="right-draw">
      <el-col :span="11" :offset="1" class="right-draw" id="video-box">
        <div>
          <el-button
            @click="getPhoto"
@@ -135,27 +162,12 @@
            >拍照取图</el-button
          >
          <el-button @click="getNewPhoto" v-else>重新取图</el-button>
          <el-button @click="clearDraw">重绘标记</el-button>
          <el-button @click="++drawIndex">重绘下一个</el-button>
          <el-button type="primary" @click="clearDraw">重绘标记</el-button>
          <el-button type="primary" @click="++drawIndex">重绘下一个</el-button>
        </div>
        <div class="right-draw-content">
          <img
            v-if="algorithmManage.form.value.liveMap"
            :src="algorithmManage.form.value.liveMap"
            class="drawImg"
            alt=""
          />
          <template v-else>
            <video controls ref="videoEle" class="drawImg">
              <source src="@/assets/movie.mp4" type="video/mp4" />
            </video>
            <canvas id="canvas"></canvas>
          </template>
          <svg
            @click="pointDraw"
            v-if="drawId && algorithmManage.form.value.liveMap"
            class="drawSvg"
          >
          <template v-if="algorithmManage.form.value.liveMap">
            <svg @click="pointDraw" v-if="drawId" class="drawSvg">
              <title>多边形</title>
              <polygon
                :fill="item.fill"
@@ -166,6 +178,13 @@
                :key="item.key"
              />
            </svg>
            <img :src="ImgUrl" class="drawImg" alt="" />
          </template>
          <template v-else>
            <!--  v-else -->
            <canvas id="video-canvas" class="drawImg"></canvas>
            <canvas id="canvas" style="display: none"></canvas>
          </template>
        </div>
        <div class="right">
          <el-button type="primary" @click="submit">确认</el-button>
@@ -176,7 +195,11 @@
      </el-col>
    </el-row>
  </el-dialog>
  <el-dialog :title="unref(aiAlgorithm.form).algorithmName" v-model="aiAlgorithm.visible.value" width="50%">
  <el-dialog
    :title="unref(aiAlgorithm.form).algorithmName"
    v-model="aiAlgorithm.visible.value"
    width="50%"
  >
    <el-form
      ref="algorithmManage.formRef"
      :model="aiAlgorithm.form"
@@ -203,13 +226,51 @@
      <el-form-item label="标签阈值" prop="labelThreshold">
        <el-input v-model="unref(aiAlgorithm.form).labelThreshold" />
      </el-form-item>
      <el-table  :data="wrapList" >
        <el-table-column label="识别标签" align="center" prop="md5Result" />
        <el-table-column label="标签阈值" align="center" prop="fileSize" />
        <el-table-column label="状态" align="center" prop="postSort" />
        <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
      <el-table :data="unref(aiAlgorithmLabel.list)" class="labels-table">
        <el-table-column label="识别标签" align="center" prop="remark" />
        <el-table-column label="标签阈值" align="center" prop="labelThreshold">
          <template #default="scope">
            <span v-if="scope.row.isEdit">
              <template v-if="unref(algorithmManage.form).id">
                <el-input
                  v-model="unref(aiAlgorithmLabel.form).labelThreshold"
                  placeholder="请输入标签阈值"
                />
              </template>
              <template v-else>
                <el-input
                  v-model="scope.row.labelThreshold"
                  placeholder="请输入标签阈值"
                />
              </template>
            </span>
            <span v-else>{{ scope.row.labelThreshold }}</span>
          </template>
        </el-table-column>
        <el-table-column label="状态" align="center" prop="postSort">
          <template #default="scope">
            <el-switch
              v-model="scope.row.status"
              :active-value="1"
              :inactive-value="0"
              @change="(e, e1) => changeStatus(scope.row, e, e1)"
            />
          </template>
        </el-table-column>
        <el-table-column
          label="操作"
          width="180"
          align="center"
          class-name="small-padding fixed-width"
        >
          <template #default="scope">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:post:edit']">修改</el-button>
            <el-button
              link
              type="primary"
              @click="aiAlgorithmLabelEdit(scope.row, scope.$index)"
              v-hasPermi="['system:post:edit']"
              >{{ scope.row.isEdit ? "确定" : "修改阈值" }}</el-button
            >
            <!-- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:post:remove']">删除</el-button> -->
          </template>
        </el-table-column>
@@ -226,26 +287,129 @@
</template>

<script setup>
import { reactify } from "@vueuse/core";
import { onMounted, ref, unref } from "vue";
const { algorithmManage } = defineProps({ algorithmManage: Object });
import { computed, onMounted, ref, unref, watch } from "vue";
const { algorithmManage, areaManger } = defineProps({
  algorithmManage: Object,
  areaManger: Object,
});
import { useIndex } from "../hooks";
import { colors, drawImage } from "../utils";
import { colors, base64ToFile } from "../utils";
import request from "@/utils/request";
import "@/utils/jsmpeg.min";
const device = useIndex({
  list: "/yunshou/aiDevice/list",
});
const aiAlgorithm = useIndex({
const aiAlgorithm = useIndex(
  {
    list: "/yunshou/aiAlgorithmConfig/list",
    detail: "/yunshou/aiAlgorithmConfig/",
  },
  () => {},
  null,
  false
);

onMounted(() => {
  aiAlgorithm.search.value.algorithmStatus = 0;
  aiAlgorithm.getList();
});
const rules = {
  // 区域管理
  regionalId: [{ required: true, message: "请选择上级区域" }],
  locationName: [
    { required: true, message: "请输入点位名称", trigger: "blur" },
  ],
  // 摄像头名称
  deviceId: [{ required: true, message: "请选择摄像头", trigger: "change" }],
  // RTSP地址
  videoUrl: [{ required: true, message: "请输入RTSP地址", trigger: "blur" }],
  // // 是否重新拉流
  // restartFlow: [
  //   { required: true, message: "请选择是否重新拉流", trigger: "change" },
  // ],
  // 是否重新拉流
  videoUrl: [{ required: true, message: "请输入视频流" }],
  // 重新拉流时间
  reStreamingTime: [
    { required: true, message: "请输入重新拉流时间", trigger: "blur" },
  ],
  // 拉流间隔时间
  inferenceInterval: [
    { required: true, message: "请输入拉流间隔时间", trigger: "blur" },
  ],
};
const algLevelRef = ref(null);
watch(
  () => algorithmManage.visible.value,
  () => {
    if (algorithmManage.visible.value) {
      setTimeout(() => {
        algLevelRef.value.resetFields();
        algorithmManage.form.value = {
          regionalId: algorithmManage.search.value.regionalId,
        };
      });
    }
  }
);
const isEdit = false;
let aiAlgorithmLabel = useIndex(
  //新增调用算法label
  {
    list: "/yunshou/aiAlgorithmLabel/list",
    detail: "/yunshou/aiAlgorithmLabel/",
    edit: "/yunshou/aiAlgorithmLabel/",
  },
  () => {},
  null,
  false
);
watch(
  () => algorithmManage.form.value,
  (e) => {
    aiAlgorithmLabel = useIndex(
      //编辑调用当前标签的label
      {
        list: "/yunshou/aiRegionalLocationAlgorithmLabel/list",
        detail: "/yunshou/aiRegionalLocationAlgorithmLabel/",
        edit: "/yunshou/aiRegionalLocationAlgorithmLabel",
      },
      () => {},
      null,
      false
    );
  }
);
function handleDeviceChange(e, index) {
  const d = device.list.value.find((x) => x.id == e);
  algorithmManage.form.value.videoUrl = d.videoUrl;
  getVideos();
}
onMounted(() => {
  getVideos();
});

const ImgUrl = computed(() => {
  const url = algorithmManage.form.value.liveMap;
  if (url) {
    // 是否为base64
    if (url.indexOf("data:image/png;base64") != -1) {
      return url;
    } else {
      return import.meta.env.VITE_IMG_BASE_PATH + url;
    }
  }
  return "";
});

function submit() {
  algLevelRef.value.validate(async (valid) => {
    if (valid) {
      let algorithms = [];
  algorithmManage.form.value.runningState = '0';
      algorithmManage.form.value.runningState = "0";
      if (algorithmManage.form.value.liveMap) {
        await toUpload(); //上传图片
      }
      if (algorithmManage.form.value.id) {
        algorithms = unref(algorithmManage.form).aiRegionalLocationList;
      } else {
@@ -265,6 +429,41 @@ function submit() {
      algorithmManage.form.value.algorithms = algorithms;
      algorithmManage.toSubmit();
    }
  });
}
function aiAlgorithmLabelEdit(scope) {
  if (algorithmManage.form.value.id) {
    //编辑调用当前标签的label
    if (!scope.isEdit) {
      aiAlgorithmLabel.toEdit(scope);
    }
    if (scope.isEdit) {
      aiAlgorithmLabel.toSubmit();
    }
  } else {
  }
  scope.isEdit = !scope.isEdit;
}
function toUpload() {
  if (
    algorithmManage.form.value.liveMap.indexOf("data:image/png;base64") == -1
  ) {
    return Promise.resolve();
  }
  const file = base64ToFile(algorithmManage.form.value.liveMap);
  const formData = new FormData();
  formData.append("file", file);
  return request({
    url: "/common/upload",
    method: "post",
    data: formData,
    headers: {
      "Content-Type": "multipart/form-data",
    },
  }).then((res) => {
    algorithmManage.form.value.liveMap = res.fileName;
  });
}
const configIndex = ref(0);
function aiConfig(index, e) {
  configIndex.value = index;
@@ -275,6 +474,39 @@ function aiConfig(index,e) {
        algorithmName: current.algorithmName,
      };
  aiAlgorithm.visible.value = true;
  if (algorithmManage.form.value.id) {
    aiAlgorithmLabel.search.value.regionalLocationAlgorithmId = e.id;
  } else {
    aiAlgorithmLabel.search.value.algorithmId = e.id;
  }
  aiAlgorithmLabel.search.value.showStatus = 0;
  aiAlgorithmLabel.getList().then(() => {
    if (current.params) {
      aiAlgorithm.form.value = {
        ...current.params,
        ...unref(aiAlgorithm.form),
      };
      if (current.params.labels) {
        //编辑标签
        current.params.labels.forEach((x) => {
          const index = unref(aiAlgorithmLabel.list).findIndex(
            (y) => y.id == x.id
          );
          if (index > -1) {
            unref(aiAlgorithmLabel.list)[index].labelThreshold =
              x.labelThreshold;
            unref(aiAlgorithmLabel.list)[index].isEdit = false;
            unref(aiAlgorithmLabel.list)[index].status = x.status;
          }
        });
      }
    }
  });
}

function changeStatus(scope, e, e1) {
  scope.isEdit = false; //通过isEdit来判断是否发生变化
  aiAlgorithmLabel.editSubmit(scope);
}
// 确认配置
function sureConfig() {
@@ -287,7 +519,11 @@ function sureConfig() {
      ...unref(aiAlgorithm.form),
    };
  } else {
    const labels = unref(aiAlgorithmLabel.list).filter((x) => "isEdit" in x);
    unref(aiAlgorithm.list)[configIndex.value].params = unref(aiAlgorithm.form);
    unref(aiAlgorithm.list)[configIndex.value].params.labels = labels.map((x) =>
      unref(x)
    );
  }
  aiAlgorithm.toClose();
}
@@ -295,16 +531,47 @@ let drawId = ref();
let drawIndex = ref(0);
const graph = reactive({});

const videoEle = ref(null);
const isPhoto = ref(false);
function getPhoto() {
  algorithmManage.form.value.liveMap = drawImage(videoEle.value);
  isPhoto.value = true;
}
let player;
function getVideos() {
  let canvas = document.getElementById("video-canvas");
  let url = algorithmManage.form.value.videoUrl;
  if (player) {
    player.destroy();
    player = null;
  }
  player = new JSMpeg.Player("ws://192.168.4.232:9999/rtsp?url=" + btoa(url), {
    canvas: canvas,
    disableGl: true,
    onVideoDecode: function (decoder, time) {
      // 视频帧解码后立即截图
      if (isPhoto.value) {
        var url = canvas.toDataURL("image/png");
        algorithmManage.form.value.liveMap = url;
        isPhoto.value = false;
      }
      // 可以在这里保存图片或者进行其他操作
    },
  });
}

/* 重新取图 */
function getNewPhoto() {
  unref(algorithmManage.form).liveMap = "";
  for (let key in graph) {
    graph[key] = [];
  }
  if (unref(algorithmManage.form).id) {
    unref(algorithmManage.form).aiRegionalLocationList.forEach((x) => {
      x.drawingArea = [];
    });
  }
  setTimeout(() => {
    getVideos();
  });
}
function beginDraw(e) {
  drawId.value = e.id;
@@ -340,6 +607,10 @@ function pointDraw(e) {
  border-radius: 5px;
  padding: 0;
}
.left-list {
  flex: 1;
  overflow: auto;
}
.form-title::before {
  content: "";
  display: inline-block;
@@ -352,6 +623,7 @@ function pointDraw(e) {
.drawImg {
  width: 100%;
  height: 100%;
  object-fit: fill;
}
.right {
  text-align: right;
@@ -362,6 +634,7 @@ function pointDraw(e) {
  gap: 20px;
  .right-draw-content {
    position: relative;
    height: 500px;
    svg {
      position: absolute;
      left: 0;
@@ -371,4 +644,11 @@ function pointDraw(e) {
    }
  }
}
.row-box {
  height: 620px;
}
.labels-table {
  height: 200px;
  margin-bottom: 20px;
}
</style>
 No newline at end of file
+17 −4
Original line number Diff line number Diff line
@@ -51,7 +51,7 @@ function useIndex(apis, callback, listReady, immList = true ) {
    });
  }
  function getList(params, isFirst) {
    request({
    return request({
      url: apis.list,
      method: "get",
      params: { ...params, ...page.value, ...search.value },
@@ -73,6 +73,20 @@ function useIndex(apis, callback, listReady, immList = true ) {
      return res;
    });
  }

  function editSubmit(data) {
    request({
      url: apis.edit,
      method: "put",
      data,
    }).then((res) => {
      if (immclose) {
        visible.value = false;
        getList();
      }
      resolve();
    });
  }
  function toSubmit(immclose = true) {
    return new Promise((resolve, reject) => {
      if (form.value.id) {
@@ -129,9 +143,8 @@ function useIndex(apis, callback, listReady, immList = true ) {
    getList,
    toOpen,
    search,
    editSubmit
  };
}



export { useIndex };
+31 −20
Original line number Diff line number Diff line
@@ -88,6 +88,11 @@
                align="center"
                prop="deviceName"
              />
              <el-table-column
                label="点位名称"
                align="center"
                prop="locationName"
              />
              <el-table-column
                label="所属区域"
                align="center"
@@ -169,7 +174,13 @@
        </el-scrollbar>
      </el-col>
    </el-row>
    <drawPoint ref="drawPointRef" :algorithmManage="algorithmManage" />
    <template v-if="algorithmManage.visible.value">
      <drawPoint
        ref="drawPointRef"
        :algorithmManage="algorithmManage"
        :areaManger="areaManger"
      />
    </template>
    <el-dialog
      title="新增区域"
      v-model="areaManger.visible.value"
@@ -185,7 +196,6 @@
            :props="{ children: 'children', value: 'id', checkStrictly: true }"
            placeholder="请选择上级区域"
            clearable
            @change="handleChange"
          />
        </el-form-item>
        <el-form-item label="区域名称" prop="regionName">
@@ -362,7 +372,7 @@ const areaManger = useIndex(
  (data) => {
    algorithmManage.form.value.regionalId = data[0].id;
    algorithmManage.search.value.regionalId = data[0].id;
    algorithmManage.getList()
    algorithmManage.getList();
  }
);

@@ -392,21 +402,22 @@ function submitAreaForm(e) {
function handleNodeClick(e) {
  algorithmManage.form.value.regionalId = e.id;
  algorithmManage.search.value.regionalId = e.id;
  algorithmManage.search.value.status = 0;
  areaManger.form.value.id = e.id;
  algorithmManage.getList({ regionalId: e.id });
}

function changeState(data, e) {
  request({
      url:'/yunshou/aiRegionalLocation/updateRunningState',
      method:'POST',
    url: "/yunshou/aiRegionalLocation/updateRunningState",
    method: "POST",
    data: {
      id: data.id,
        runningState:e
      }
      runningState: e,
    },
  }).then(() => {
      algorithmManage.getList({regionalId:data.regionalId})
    })
    algorithmManage.getList({ regionalId: data.regionalId });
  });
}
</script>
  
+24 −6
Original line number Diff line number Diff line
@@ -7,13 +7,31 @@ export const colors = () => {
  return `rgba(${r},${g},${b},${a})`;
};


export function drawImage(video) {
  const img = video;
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");
  canvas.width = img.offsetWidth;
  canvas.height = img.offsetHeight;
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height); // 绘制图片
  return canvas.toDataURL("image/jpg")
  return canvas.toDataURL("image/jpg");
}

export function base64ToFile(base64) {
  const name = new Date().getTime();
  if (typeof base64 != "string") {
    return;
  }
  var arr = base64.split(",");
  var type = arr[0].match(/:(.*?);/)[1];
  var fileExt = type.split("/")[1];
  var bstr = atob(arr[1]);
  var n = bstr.length;
  var u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], `${name}.` + fileExt, {
    type: type,
  });
}
Loading