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

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

parents 951203a4 4c844266
<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="算法名称"
......@@ -118,7 +145,7 @@
<el-button
link
type="primary"
@click="aiConfig(scope.$index,scope.row)"
@click="aiConfig(scope.$index, scope.row)"
v-hasPermi="['system:post:remove']"
>配置</el-button
>
......@@ -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">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:post:edit']">修改</el-button>
<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"
@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 {
......@@ -264,9 +428,44 @@ 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) {
function aiConfig(index, e) {
configIndex.value = index;
const current = unref(aiAlgorithm.list)[index];
aiAlgorithm.form.value = algorithmManage.form.value.id
......@@ -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
import { onMounted, ref, watch } from "vue";
import request from "@/utils/request";
import { ElMessageBox } from "element-plus";
function useIndex(apis, callback, listReady, immList = true ) {
function useIndex(apis, callback, listReady, immList = true) {
const visible = ref(false);
const search = ref({});
const form = ref({
......@@ -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 };
......@@ -88,6 +88,11 @@
align="center"
prop="deviceName"
/>
<el-table-column
label="点位名称"
align="center"
prop="locationName"
/>
<el-table-column
label="所属区域"
align="center"
......@@ -126,7 +131,7 @@
v-model="scope.row.runningState"
active-value="0"
inactive-value="1"
@change="(e)=>changeState(scope.row, e)"
@change="(e) => changeState(scope.row, e)"
/>
</template>
</el-table-column>
......@@ -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();
}
);
......@@ -375,7 +385,7 @@ const algorithmManage = useIndex(
detail: "/yunshou/aiRegionalLocation/",
},
({ data }) => {},
()=>{},
() => {},
false
);
function submitAreaForm(e) {
......@@ -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',
data:{
id:data.id,
runningState:e
}
}).then(()=>{
algorithmManage.getList({regionalId:data.regionalId})
})
url: "/yunshou/aiRegionalLocation/updateRunningState",
method: "POST",
data: {
id: data.id,
runningState: e,
},
}).then(() => {
algorithmManage.getList({ regionalId: data.regionalId });
});
}
</script>
......
......@@ -7,13 +7,31 @@ export const colors = () => {
return `rgba(${r},${g},${b},${a})`;
};
export function drawImage(video){
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,
});
}
......@@ -31,7 +31,8 @@ export default defineConfig(({ mode, command }) => {
proxy: {
// https://cn.vitejs.dev/config/#server-proxy
'/dev-api': {
target: 'http://192.168.3.82/ai',
target: 'http://192.168.3.82:80/ai',
// target: 'http://192.168.4.206:80/ai',
// target:'http://192.168.14.43:8111/ai',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment