You need to sign in or sign up before continuing.
Commit 2cf53cfe authored by lei's avatar lei

add:

parent d175353f
...@@ -7,5 +7,7 @@ ENV = 'development' ...@@ -7,5 +7,7 @@ ENV = 'development'
# 若依管理系统/开发环境 # 若依管理系统/开发环境
VUE_APP_BASE_API = '/dev-api' VUE_APP_BASE_API = '/dev-api'
VUE_APP_WS_URL = 'ws://192.168.2.16:8081'
# 路由懒加载 # 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true VUE_CLI_BABEL_TRANSPILE_MODULES = true
...@@ -6,3 +6,4 @@ ENV = 'production' ...@@ -6,3 +6,4 @@ ENV = 'production'
# 若依管理系统/生产环境 # 若依管理系统/生产环境
VUE_APP_BASE_API = '/prod-api' VUE_APP_BASE_API = '/prod-api'
VUE_APP_WS_URL = '/ws-api'
...@@ -21,3 +21,4 @@ selenium-debug.log ...@@ -21,3 +21,4 @@ selenium-debug.log
package-lock.json package-lock.json
yarn.lock yarn.lock
public/video/webrtc-streamer.exe
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script type="text/javascript" src="./adapter.min.js"></script>
<script type="text/javascript" src="./webrtcstreamer.js"></script>
</head>
<script>
window.onload = function () {
this.webRtcServer = new WebRtcStreamer("video","http://127.0.0.1:8000/");
webRtcServer.connect(
"rtsp://admin:Gemho120611@192.168.0.15:554/h264/ch1/main/av_stream","rtptransport=tcp&timeout=60"
);
};
window.onbeforeunload = function () {
this.webRtcServer.disconnect();
};
</script>
</head>
<body>
<video id="video" muted playsinline controls></video>
</body>
</html>
This diff is collapsed.
...@@ -7,18 +7,61 @@ ...@@ -7,18 +7,61 @@
<script> <script>
import ThemePicker from "@/components/ThemePicker"; import ThemePicker from "@/components/ThemePicker";
import MessageBox from "@/components/MessageBox/index.vue";
export default { export default {
name: "App", name: "App",
components: { ThemePicker }, components: { ThemePicker, MessageBox },
data() {
return {
socket: null, // WebSocket实例
baseWsUrl: process.env.VUE_APP_WS_URL, // WebSocket服务器地址
};
},
metaInfo() { metaInfo() {
return { return {
title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title, title:
titleTemplate: title => { this.$store.state.settings.dynamicTitle &&
return title ? `${title} - ${process.env.VUE_APP_TITLE}` : process.env.VUE_APP_TITLE this.$store.state.settings.title,
titleTemplate: (title) => {
return title
? `${title} - ${process.env.VUE_APP_TITLE}`
: process.env.VUE_APP_TITLE;
},
};
},
methods: {
showAlarm(data) {
this.$notify({
title: data.alarmMessage,
dangerouslyUseHTMLString: true,
message: this.$createElement("MessageBox", {
props: { message: data },
}),
// duration: 0,
});
},
},
mounted() {
if (this.socket) {
return;
}
// 初始化WebSocket连接
this.socket = new WebSocket(this.baseWsUrl + "/websocket");
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Received message:", data);
if (data.alarmImageUrl) {
this.showAlarm(data);
} }
};
},
beforeDestroy() {
// 确保在组件销毁时关闭WebSocket连接
if (this.socket) {
this.socket.close();
} }
} },
}; };
</script> </script>
<style scoped> <style scoped>
......
...@@ -32,4 +32,17 @@ export function delAlgorithm(id) { ...@@ -32,4 +32,17 @@ export function delAlgorithm(id) {
url: '/system/algorithmConfig/' + id, url: '/system/algorithmConfig/' + id,
method: 'delete' method: 'delete'
}) })
}
//修改算法配置状态
export function changeAlgorithmStatus(list, type) {
const data = {
list,
type
}
return request({
url: '/system/algorithmConfig/selectAlgorithmConfig',
method: 'post',
data: data
})
} }
\ No newline at end of file
import request from '@/utils/request'
//摄像头离在线数据
export function getCameraOnlineData(query) {
return request({
url: '/system/cameraConfig/cameraOnlineOffline',
method: 'get',
params: query
})
}
//获取视频分析任务离在线数据
export function getVideoAnalysisOnlineData(query) {
return request({
url: '/system/task/taskOnlineOffline',
method: 'get',
params: query
})
}
//统计分析
export function getStatisticsData(query) {
return request({
url: '/system/log/listAlarmLogByTime?startTime=' + query.startTime + '&endTime=' + query.endTime,
method: 'get',
})
}
//获取报警信息统计
export function getAlarmStatisticsData(query) {
return request({
url: '/system/log/alarmOverview',
method: 'get',
})
}
//获取摄像播放地址
export function getVideoPlayUrl(query) {
return request({
url: '/system/cameraConfig/getPlayUrl?cameraId=' + query.cameraId,
method: 'get',
})
}
\ No newline at end of file
...@@ -31,4 +31,12 @@ export function delVideoAnalysisTask(id) { ...@@ -31,4 +31,12 @@ export function delVideoAnalysisTask(id) {
url: '/system/task/' + id, url: '/system/task/' + id,
method: 'delete' method: 'delete'
}) })
}
//抓取截图
export function getVideoAnalysisTaskScreenshot(id) {
return request({
url: '/system/cameraConfig/getImage?cameraId=' + id,
method: 'get'
})
} }
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1747879145937" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3645" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M895.8 736.4c0-33.6 7.2-166.2-97.4-271.4-70.4-70.8-160.6-106.8-286.6-112.4V192L128 448l384 256V544.4c80 2.2 124.8 18.2 173.4 40C747.2 612 796 672.4 837 737.6l38.4 62.4H896c0-20.2-0.2-45.8-0.2-63.6z" fill="currentColor" p-id="3646"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1747878450842" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2632" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.1953125" height="200"><path d="M358.5 1021.5c-52.93 0-96-43.07-96-96 0-13.12 2.6-25.84 7.74-37.81l0.12-0.27-141.99-184.66-0.34 0.11a95.806 95.806 0 0 1-29.53 4.64c-52.93 0-96-43.07-96-96s43.07-96 96-96c7.56 0 15.09 0.89 22.41 2.64l0.36 0.09 213.67-370.08-0.16-0.25c-9.34-15.12-14.28-32.54-14.28-50.39 0-52.93 43.07-96 96-96 40.36 0 76.66 25.5 90.34 63.46l0.09 0.26 300.27 61.72 0.19-0.26c18.01-25.16 47.21-40.18 78.11-40.18 52.93 0 96 43.07 96 96 0 36.49-20.26 69.36-52.88 85.79l-0.31 0.16 41.76 393.5 0.25 0.12a96.184 96.184 0 0 1 39.11 34.79c9.85 15.41 15.06 33.27 15.06 51.65 0 52.93-43.07 96-96 96-31.18 0-60.53-15.24-78.52-40.76l-0.22-0.31-396.7 138.78-0.05 0.29c-8.2 45.76-47.94 78.97-94.5 78.97z m0-192c34.03 0 64.82 17.44 82.36 46.66l0.21 0.35L832.93 739.4l0.03-0.32c3.81-39.11 31.82-72.49 69.7-83.05l0.41-0.11-40.34-380.14-0.34-0.08c-39.73-9.82-68.86-43.81-72.49-84.56l-0.03-0.37-283.9-58.36-0.15 0.39c-7 17.7-19.01 32.81-34.72 43.69-16.08 11.13-34.96 17.02-54.59 17.02-9.75 0-19.37-1.46-28.59-4.33l-0.38-0.12L176.19 555.1l0.19 0.26c11.86 16.42 18.12 35.83 18.12 56.14 0 17.74-4.88 35.06-14.11 50.11l-0.18 0.29 136.28 177.25 0.37-0.18c13.04-6.28 27.05-9.47 41.64-9.47z" p-id="2633"></path><path d="M416.5 2c40.15 0 76.26 25.37 89.87 63.13l0.19 0.53 0.55 0.11 299.68 61.6 0.64 0.13 0.38-0.53c8.66-12.1 20.19-22.15 33.34-29.06C854.71 90.77 870.06 87 885.5 87c52.66 0 95.5 42.84 95.5 95.5 0 18.03-5.05 35.58-14.6 50.77a95.81 95.81 0 0 1-38 34.57l-0.62 0.31 0.07 0.69 41.7 392.88 0.06 0.55 0.5 0.24c15.91 7.71 29.36 19.68 38.91 34.61 9.8 15.33 14.98 33.09 14.98 51.38 0 52.66-42.84 95.5-95.5 95.5-31.02 0-60.22-15.16-78.11-40.55l-0.43-0.62-0.71 0.25-396.09 138.59-0.55 0.19-0.1 0.57c-3.9 21.77-15.4 41.67-32.39 56.03-17.2 14.54-39.08 22.54-61.62 22.54-52.66 0-95.5-42.84-95.5-95.5 0-13.05 2.59-25.71 7.7-37.61l0.23-0.54-0.36-0.46-141.59-184.16-0.43-0.56-0.67 0.22A95.43 95.43 0 0 1 98.5 707C45.84 707 3 664.16 3 611.5S45.84 516 98.5 516c7.52 0 15.01 0.88 22.29 2.62l0.73 0.17 0.37-0.65 213.33-369.5 0.3-0.52-0.31-0.51c-9.29-15.04-14.2-32.37-14.2-50.12C321 44.84 363.84 2 416.5 2m0 191c-9.7 0-19.27-1.45-28.44-4.31l-0.76-0.24-0.4 0.69-210.98 365.43-0.32 0.56 0.38 0.52C187.77 571.99 194 591.3 194 611.5c0 17.64-4.85 34.88-14.03 49.85l-0.36 0.59 0.42 0.55 135.82 176.65 0.5 0.64 0.73-0.35c12.97-6.26 26.91-9.43 41.42-9.43 16.79 0 33.3 4.42 47.75 12.78a96.095 96.095 0 0 1 34.18 33.63l0.42 0.7 0.77-0.27 391.17-136.87 0.6-0.21 0.06-0.64c1.88-19.33 9.53-37.41 22.11-52.28 12.44-14.71 28.77-25.2 47.22-30.35l0.82-0.23-0.09-0.84-40.26-379.37-0.07-0.7-0.68-0.17c-39.52-9.77-68.5-43.58-72.11-84.12l-0.07-0.74-0.73-0.15-283.12-58.2-0.82-0.17-0.31 0.78c-6.96 17.61-18.91 32.64-34.54 43.46-15.99 11.11-34.77 16.96-54.3 16.96m0-192C363.2 1 320 44.2 320 97.5c0 18.58 5.26 35.93 14.35 50.65l-213.33 369.5A96.669 96.669 0 0 0 98.5 515C45.2 515 2 558.2 2 611.5S45.2 708 98.5 708a96.5 96.5 0 0 0 29.69-4.66l141.59 184.15c-5 11.66-7.78 24.51-7.78 38.01 0 53.3 43.2 96.5 96.5 96.5 47.46 0 86.91-34.25 94.99-79.39l396.08-138.59C867.04 828.81 895.88 845 928.5 845c53.3 0 96.5-43.2 96.5-96.5 0-38.22-22.23-71.26-54.46-86.88l-41.7-392.88C960.37 252.86 982 220.21 982 182.5c0-53.3-43.2-96.5-96.5-96.5-32.37 0-61.01 15.94-78.51 40.39l-299.68-61.6C493.91 27.59 458.31 1 416.5 1z m0 193c40.77 0 75.63-25.28 89.77-61.03l283.12 58.2c3.69 41.4 33.52 75.27 72.87 85l40.26 379.37c-37.74 10.53-66.15 43.41-70.06 83.49L441.29 875.9c-16.87-28.1-47.63-46.9-82.79-46.9-15 0-29.2 3.42-41.86 9.53L180.82 661.88c8.99-14.66 14.18-31.91 14.18-50.38 0-21.07-6.76-40.56-18.22-56.43l210.98-365.42c9.08 2.83 18.73 4.35 28.74 4.35z" fill="currentColor" p-id="2634"></path></svg>
\ No newline at end of file
This image diff could not be displayed because it is too large. You can view the blob instead.
...@@ -13,7 +13,9 @@ body { ...@@ -13,7 +13,9 @@ body {
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Arial, sans-serif; Microsoft YaHei, Arial, sans-serif;
} }
.el-radio__original {
display: none !important; /* 隐藏原生 radio 输入,但仍然允许交互 */
}
label { label {
font-weight: 700; font-weight: 700;
} }
......
...@@ -63,7 +63,7 @@ export default { ...@@ -63,7 +63,7 @@ export default {
data: { data: {
type: Object, type: Object,
}, },
// 图片数量限制 // 文件数量限制
limit: { limit: {
type: Number, type: Number,
default: 1, default: 1,
...@@ -92,7 +92,7 @@ export default { ...@@ -92,7 +92,7 @@ export default {
dialogVisible: false, dialogVisible: false,
hideUpload: false, hideUpload: false,
baseUrl: process.env.VUE_APP_BASE_API, baseUrl: process.env.VUE_APP_BASE_API,
uploadImgUrl: process.env.VUE_APP_BASE_API + this.action, // 上传的图片服务器地址 uploadImgUrl: process.env.VUE_APP_BASE_API + this.action, // 上传的文件服务器地址
headers: { headers: {
Authorization: "Bearer " + getToken(), Authorization: "Bearer " + getToken(),
}, },
...@@ -151,7 +151,7 @@ export default { ...@@ -151,7 +151,7 @@ export default {
if (!isImg) { if (!isImg) {
this.$modal.msgError( this.$modal.msgError(
`文件格式不正确,请上传${this.fileType.join("/")}图片格式文件!` `文件格式不正确,请上传${this.fileType.join("/")}文件格式文件!`
); );
return false; return false;
} }
...@@ -162,11 +162,11 @@ export default { ...@@ -162,11 +162,11 @@ export default {
if (this.fileSize) { if (this.fileSize) {
const isLt = file.size / 1024 / 1024 < this.fileSize; const isLt = file.size / 1024 / 1024 < this.fileSize;
if (!isLt) { if (!isLt) {
this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`); this.$modal.msgError(`上传头像文件大小不能超过 ${this.fileSize} MB!`);
return false; return false;
} }
} }
this.$modal.loading("正在上传图片,请稍候..."); this.$modal.loading("正在上传文件,请稍候...");
this.number++; this.number++;
}, },
// 文件个数超出 // 文件个数超出
...@@ -186,7 +186,7 @@ export default { ...@@ -186,7 +186,7 @@ export default {
this.uploadedSuccessfully(); this.uploadedSuccessfully();
} }
}, },
// 删除图片 // 删除文件
handleDelete(file) { handleDelete(file) {
const findex = this.fileList.map((f) => f.name).indexOf(file.name); const findex = this.fileList.map((f) => f.name).indexOf(file.name);
if (findex > -1) { if (findex > -1) {
...@@ -196,7 +196,7 @@ export default { ...@@ -196,7 +196,7 @@ export default {
}, },
// 上传失败 // 上传失败
handleUploadError() { handleUploadError() {
this.$modal.msgError("上传图片失败,请重试"); this.$modal.msgError("上传文件失败,请重试");
this.$modal.closeLoading(); this.$modal.closeLoading();
}, },
// 上传结束处理 // 上传结束处理
......
<template>
<div class="alarmmsg">
<el-row>
<el-col :span="12">
<p>
<dict-tag
:options="dict.type.algorithm_level"
:value="message.alarmLevel"
class="alarmmsg-dict"
/>
<span>{{ message.algorithmName }}</span>
</p>
<p>{{ message.cameraName }}</p>
<p>{{ message.alarmTime }}</p>
</el-col>
<el-col :span="12">
<img
:src="'data:image/path;base64,' + message.alarmImageUrl"
class="alarmmsg-img"
/>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
dicts: ["algorithm_level"],
name: "messageBox",
props: {
message: {
type: Object,
default: {
alarmImageUrl: "",
alarmLevel: "",
alarmMessage: "",
alarmTime: "",
cameraPosition: "",
},
},
},
components: {},
data() {
return {};
},
computed: {},
watch: {
message: {
handler(newVal, oldVal) {},
deep: true,
},
},
created() {},
mounted() {},
methods: {},
};
</script>
<style scoped lang="scss">
.alarmmsg {
width: 300px;
height: 100px;
p {
margin-bottom: 10px;
}
.alarmmsg-dict {
display: inline-block;
}
.alarmmsg-img {
width: 100px;
height: 80px;
}
}
</style>
<template>
<!-- <map-video :showV="videoShow" :rtspIp="rtspIp"></map-video> -->
<video
:id="videoID"
class="video-js"
style="object-fit: fill"
controls
autoplay
autobuffer
muted
preload="auto"
></video>
</template>
<script>
import "video.js/dist/video-js.css";
import videojs from "video.js";
export default {
name: "TanchengkjWebDialogCom",
props: {
videoID: {
type: String,
default() {
return "videoID";
},
},
rtspIp: {
type: String,
default() {
return "";
},
},
videoShow: {
type: Boolean,
default() {
return false;
},
},
videoPlayName: {
type: String,
default() {
return "";
},
},
},
data() {
return {
dialogFromVisible: this.videoShow,
inDate: "",
t2: null,
getUrl: "http://127.0.0.1:8000",
player: null,
webRtcServer: null,
webRtcList: [],
playerList: [],
videoSeting: {
language: "zh-CN",
autoplay: true, // true/false 播放器准备好之后,是否自动播放 【默认false】
controls: true, // /false 是否拥有控制条 【默认true】,如果设为false ,那么只能通过api进行控制了。也就是说界面上不会出现任何控制按钮
/* height: 100, // 视频容器的高度,字符串或数字 单位像素 比如: height:300 or height:‘300px‘
width: 100, // 视频容器的宽度, 字符串或数字 单位像素*/
loop: false, // /false 视频播放结束后,是否循环播放
muted: true, // /false 是否静音
poster: "", // 播放前显示的视频画面,播放开始之后自动移除。通常传入一个URL
preload: "auto", // 预加载 ‘auto‘ 自动 ’metadata‘ 元数据信息 ,比如视频长度,尺寸等 ‘none‘ 不预加载任何数据,直到用户开始播放才开始下载
bigPlayButton: true,
},
};
},
watch: {
videoShow: {
immediate: false,
handler: function (value) {
this.dialogFromVisible = value;
if (value == false) {
console.log("视频关闭了!");
// this.playerList[0].pause();
// this.webRtcList[0].webRtcServer.disconnect();
} else {
this.$forceUpdate();
console.log("输出一下rtsp:", this.rtspIp);
setTimeout(() => {
this.getWebRtc();
}, 1000);
}
},
},
height: {
immediate: false,
handler: function (value) {
this.$forceUpdate();
},
},
},
mounted() {
let that = this;
this.initNDate();
let procedure1 = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("1执行crtWebRtc成功!");
that.crtWebRtc();
resolve();
});
});
let procedure2 = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("2执行getvideo成功!");
that.getVideo();
resolve();
});
});
let procedure3 = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("3执行getWebRtc成功!");
that.getWebRtc();
resolve();
});
});
procedure1
.then((data) => {
return procedure2;
})
.then((data) => {
return procedure3;
});
console.log("mounted输出rtspip:", this.rtspIp);
// console.log("mounted输出videoID:", this.videoID);
// console.log("mounted输出getUrl:", this.getUrl);
},
created() {},
methods: {
/*设置当前系统时间*/
initNDate: function () {
this.t2 = setInterval(() => {
this.inDate = new Date().toLocaleString();
}, 1000);
},
getVideo() {
let that = this;
this.playerList[0] = videojs(
this.videoID,
this.videoSeting,
function onPlayerReady() {
videojs.log("Your player is ready!");
this.on("loadstart", function () {
console.log("开始请求数据 ");
});
this.on("progress", function () {
console.log("正在请求数据 ");
});
this.on("loadedmetadata", function () {
console.log("获取资源长度完成 ");
});
this.on("canplaythrough", function () {
console.log("视频源数据加载完成");
});
this.on("waiting", function () {
console.log("等待数据");
});
this.on("play", function () {
console.log("视频开始播放");
// that.getWebRtc();
});
this.on("playing", function () {
console.log("视频播放中");
});
this.on("pause", function () {
console.log("视频暂停播放");
that.webRtcList[0].webRtcServer.disconnect();
});
this.on("ended", function () {
console.log("视频播放结束");
});
this.on("error", function () {
console.log("加载错误");
});
this.on("seeking", function () {
console.log("视频跳转中");
});
this.on("seeked", function () {
console.log("视频跳转结束");
});
this.on("ratechange", function () {
console.log("播放速率改变");
});
this.on("timeupdate", function () {
console.log("播放时长改变");
});
this.on("volumechange", function () {
console.log("音量改变");
});
this.on("stalled", function () {
console.log("网速异常");
});
}
);
this.playerList.push(this.player);
console.log("playerList", this.playerList);
console.log("id_", this.playerList[0].id_);
},
crtWebRtc() {
console.log("webRtcList:", this.webRtcList);
for (const item of this.webRtcList) {
if (this.videoID == item.name) {
return;
}
}
console.log("进来了开始初始化webRtc模块:", this.videoID);
this.webRtcServer = new WebRtcStreamer(
this.videoID,
// "http://192.168.1.166:8000"
this.getUrl
);
let s = {};
s.name = this.videoID;
s.webRtcServer = this.webRtcServer;
this.webRtcList.push(s);
console.log("this.webrtc哈", this.webRtcList);
},
getWebRtc() {
console.log("将rtspip传递给webRtcServer.connect:", this.rtspIp);
console.log("打印:webrtclist", this.webRtcList);
this.webRtcList[0].webRtcServer.connect(this.rtspIp, this.rtspIp);
// for (const item of this.webRtcList) {
// if (this.videoID == item.name) {
// return;
// }
// item.webRtcServer.connect(this.rtspIp);
// }
},
},
beforeDestroy() {
console.log("运行销毁前生命周期事件");
this.webRtcList[0].webRtcServer.disconnect();
if (this.player) {
this.playerList[0].dispose();
}
},
};
</script>
<style scoped>
.lf20 {
margin-left: 20px;
}
/* // 覆盖层元素增加可穿透点击事件 */
.el-dialog__wrapper {
pointer-events: none;
}
/* // 弹窗层元素不可穿透点击事件(不影响弹窗层元素的点击事件) */
.el-dialog {
pointer-events: auto;
}
.el-dialog__title {
line-height: 24px !important;
font-size: 15px !important;
color: #303133;
}
.el-dialog__header {
padding: 5px 5px 0px 5px !important;
text-align: left;
}
.el-dialog__headerbtn {
position: absolute;
top: 5px !important;
right: 5px !important;
padding: 0;
background: 0 0;
border: none;
outline: 0;
cursor: pointer;
font-size: 16px;
}
.el-dialog__body {
padding: 2px !important;
color: #606266;
font-size: 14px;
word-break: break-all;
height: 40vh;
overflow-y: auto;
}
.el-divider--horizontal {
display: block;
height: 1px;
width: 100%;
margin: 0px 0 !important;
}
.el-menu:hover {
opacity: 1 !important;
}
.ve-line {
margin-left: 10px;
padding-top: 20px;
color: #fff4fd;
width: 540px !important;
}
.el-row {
margin-bottom: 2px;
&:last-child {
margin-bottom: 0;
}
}
.el-col {
border-radius: 4px;
}
.bg-purple-dark {
background: #99a9bf;
}
.bg-purple {
background: #d3dce6;
}
.bg-purple-light {
background: #e5e9f2;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
height: 230px;
}
.row-bg {
background-color: #f9fafc;
}
.cell-v {
display: flex;
flex-direction: column;
height: 30vh;
}
.player {
height: 100%;
}
.bk-button-group {
color: #00a0e9;
}
.video-js {
width: 100%;
height: 100%;
}
.video-js .vjs-big-play-button {
top: 50%;
left: 50%;
margin-left: -1.5em;
margin-top: -1em;
}
.vjs-loading-spinner:before,
.vjs-loading-spinner:after {
content: "";
position: absolute;
margin: -6px;
-webkit-box-sizing: inherit;
box-sizing: inherit;
width: inherit;
height: inherit;
border-radius: inherit;
opacity: 1;
border: inherit;
border-color: transparent;
border-top-color: white;
display: none;
}
</style>
...@@ -13,7 +13,6 @@ import router from './router' ...@@ -13,7 +13,6 @@ import router from './router'
import directive from './directive' // directive import directive from './directive' // directive
import plugins from './plugins' // plugins import plugins from './plugins' // plugins
import { download } from '@/utils/request' import { download } from '@/utils/request'
import './assets/icons' // icon import './assets/icons' // icon
import './permission' // permission control import './permission' // permission control
import { getDicts } from "@/api/system/dict/data"; import { getDicts } from "@/api/system/dict/data";
...@@ -37,6 +36,8 @@ import DictTag from '@/components/DictTag' ...@@ -37,6 +36,8 @@ import DictTag from '@/components/DictTag'
import VueMeta from 'vue-meta' import VueMeta from 'vue-meta'
// 字典数据组件 // 字典数据组件
import DictData from '@/components/DictData' import DictData from '@/components/DictData'
//自适应组件
import VScaleScreen from 'v-scale-screen'
// 全局方法挂载 // 全局方法挂载
Vue.prototype.getDicts = getDicts Vue.prototype.getDicts = getDicts
...@@ -61,6 +62,7 @@ Vue.component('ImagePreview', ImagePreview) ...@@ -61,6 +62,7 @@ Vue.component('ImagePreview', ImagePreview)
Vue.use(directive) Vue.use(directive)
Vue.use(plugins) Vue.use(plugins)
Vue.use(VueMeta) Vue.use(VueMeta)
Vue.use(VScaleScreen)
DictData.install() DictData.install()
/** /**
......
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
<el-option <el-option
v-for="(item, index) in cameraAdderss" v-for="(item, index) in cameraAdderss"
:key="index" :key="index"
:value="item.address" :value="item.positionId"
:label="item.name" :label="item.positionName"
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
placeholder="报警处理状态" placeholder="报警处理状态"
clearable clearable
> >
<el-option :value="3" label="待处理"></el-option> <el-option :value="2" label="待处理"></el-option>
<el-option :value="1" label="正确报警"></el-option> <el-option :value="1" label="正确报警"></el-option>
<el-option :value="0" label="错误报警"></el-option> <el-option :value="0" label="错误报警"></el-option>
</el-select> </el-select>
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
></el-table-column> ></el-table-column>
<el-table-column <el-table-column
label="视频分析任务名称" label="视频分析任务名称"
prop="videoAnlysisTasksName" prop="taskName"
align="center" align="center"
></el-table-column> ></el-table-column>
<el-table-column <el-table-column
...@@ -66,11 +66,15 @@ ...@@ -66,11 +66,15 @@
prop="cameraName" prop="cameraName"
align="center" align="center"
></el-table-column> ></el-table-column>
<el-table-column <el-table-column label="报警图片" prop="alarmImageUrl" align="center">
label="报警图片" <template slot-scope="scope">
prop="alarmImg" <image-preview
align="center" :src="scope.row.alarmImageUrl"
></el-table-column> :width="60"
:height="40"
/>
</template>
</el-table-column>
<el-table-column <el-table-column
label="报警时间" label="报警时间"
prop="alarmTime" prop="alarmTime"
...@@ -81,32 +85,40 @@ ...@@ -81,32 +85,40 @@
prop="algorithmName" prop="algorithmName"
align="center" align="center"
></el-table-column> ></el-table-column>
<el-table-column <el-table-column label="算法报警等级" prop="alarmLevel" align="center">
label="算法报警等级"
prop="algorithmLevel"
align="center"
>
<template slot-scope="scope"> <template slot-scope="scope">
<dict-tag <dict-tag
:options="dict.type.algorithm_level" :options="dict.type.algorithm_level"
:value="scope.row.algorithmLevel" :value="scope.row.alarmLevel"
/> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="报警处理状态" align="center"> <el-table-column label="报警处理状态" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
{{ scope.row.alarmStatus === 0 ? "错误报警" : "正确报警" }} <el-tag v-if="scope.row.alarmStatus === 2" type="warning"
>待处理</el-tag
>
<el-tag v-if="scope.row.alarmStatus === 1" type="success"
>正确报警</el-tag
>
<el-tag v-if="scope.row.alarmStatus === 0" type="danger"
>错误报警</el-tag
>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" width="200px"> <el-table-column label="操作" align="center" width="200px">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="text" size="mini" @click="editBut(scope.row)"> <el-button type="text" size="mini" @click="editBut(scope.row, 1)">
正确报警 正确报警
</el-button> </el-button>
<el-button type="text" size="mini" @click="editBut(scope.row)"> <el-button type="text" size="mini" @click="editBut(scope.row, 0)">
错误报警 错误报警
</el-button> </el-button>
<el-button type="text" size="mini" @click="deleteBut(scope.row)"> <el-button
type="text"
size="mini"
@click="deleteBut(scope.row.logId)"
>
<i class="el-icon-delete el-icon--left"></i> <i class="el-icon-delete el-icon--left"></i>
删除 删除
</el-button> </el-button>
...@@ -130,6 +142,7 @@ import { ...@@ -130,6 +142,7 @@ import {
delAlarmLog, delAlarmLog,
updateAlarmLog, updateAlarmLog,
} from "@/api/business/alarmlog.js"; } from "@/api/business/alarmlog.js";
import { getCameraPositionList } from "@/api/business/cameraconfig.js";
export default { export default {
dicts: ["algorithm_level"], dicts: ["algorithm_level"],
name: "Alarmlog", name: "Alarmlog",
...@@ -144,8 +157,8 @@ export default { ...@@ -144,8 +157,8 @@ export default {
alarmImg: "", // 报警图片 alarmImg: "", // 报警图片
alarmTime: "", // 报警时间 alarmTime: "", // 报警时间
algorithmName: "", // 算法名称 algorithmName: "", // 算法名称
algorithmLevel: 1, // 算法报警等级 algorithmLevel: null, // 算法报警等级
alarmStatus: 0, // 报警处理状态 alarmStatus: null, // 报警处理状态
}, },
tableList: [], tableList: [],
cameraAdderss: [ cameraAdderss: [
...@@ -166,24 +179,81 @@ export default { ...@@ -166,24 +179,81 @@ export default {
watch: {}, watch: {},
created() { created() {
this.getList(); this.getList();
getCameraPositionList().then((res) => {
if (res.code == 200) {
// 处理空数组情况
const rawData = Array.isArray(res.data) ? res.data : [];
this.cameraAdderss = this.flattenJson(rawData).map((item) => ({
positionName: item.positionName,
positionId: item.positionId,
}));
} else {
this.cameraAdderss = []; // 接口异常时设为空数组
}
});
}, },
mounted() {}, mounted() {},
methods: { methods: {
// 新增扁平化方法
flattenJson(arr) {
return arr.reduce((acc, item) => {
acc.push(item);
if (item.children && item.children.length) {
acc.push(...this.flattenJson(item.children));
}
return acc;
}, []);
},
// 搜索 // 搜索
search() {}, search() {
let params = { ...this.queryParams, ...this.form };
getAlarmLogList(params).then((res) => {
if (res.code == 200) {
this.tableList = res.rows;
this.total = res.total;
}
});
},
// 删除 // 删除
deleteBut(row) { deleteBut(id) {
this.$confirm("确认删除吗?", "提示", { delAlarmLog(id).then((res) => {
confirmButtonText: "确定", if (res.code == 200) {
cancelButtonText: "取消", this.$message({
type: "warning", message: "删除成功",
type: "success",
});
this.getList();
} else {
this.$message({
message: res.msg,
type: "error",
});
}
}); });
}, },
// 编辑 // 编辑
editBut(row) {}, editBut(row, alarmStatus) {
updateAlarmLog({
logId: row.logId,
alarmStatus: alarmStatus, // 1 正确报警 0 错误报警
}).then((res) => {
if (res.code == 200) {
this.$message({
message: "操作成功",
type: "success",
});
this.getList();
} else {
this.$message({
message: res.msg,
type: "error",
});
}
});
},
// 获取数据列表 // 获取数据列表
getList() { getList() {
getAlarmLogList(this.queryParams).then((res) => { getAlarmLogList({ ...this.queryParams, ...this.form }).then((res) => {
if (res.code == 200) { if (res.code == 200) {
this.tableList = res.rows; this.tableList = res.rows;
this.total = res.total; this.total = res.total;
......
<template> <template>
<div class="app-container">报警推送</div> <div class="app-container">
<div class="Pd20">
<el-form :model="form" :inline="true" size="mini">
<el-form-item>
<el-select v-model="form.cameraName" placeholder="监控点位" clearable>
<el-option
v-for="(item, index) in cameraAdderss"
:key="index"
:value="item.positionId"
:label="item.positionName"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select
v-model="form.algorithmLevel"
placeholder="算法报警等级"
clearable
>
<el-option
v-for="(item, index) in dict.type.algorithm_level"
:key="index"
:value="item.value"
:label="item.label"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select
v-model="form.alarmStatus"
placeholder="报警处理状态"
clearable
>
<el-option :value="2" label="待处理"></el-option>
<el-option :value="1" label="正确报警"></el-option>
<el-option :value="0" label="错误报警"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-input
v-model="form.algorithmName"
placeholder="算法名称"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" size="mini" @click="search">
<i class="el-icon-search el-icon--left"></i>
搜索
</el-button>
</el-form-item>
</el-form>
<el-table :data="tableList">
<el-table-column
label="摄像头ID"
prop="cameraId"
align="center"
></el-table-column>
<el-table-column
label="视频分析任务名称"
prop="taskName"
align="center"
></el-table-column>
<el-table-column
label="摄像头名称"
prop="cameraName"
align="center"
></el-table-column>
<el-table-column label="报警图片" prop="alarmImageUrl" align="center">
<template slot-scope="scope">
<image-preview
:src="scope.row.alarmImageUrl"
:width="60"
:height="40"
/>
</template>
</el-table-column>
<el-table-column
label="报警时间"
prop="alarmTime"
align="center"
></el-table-column>
<el-table-column
label="算法名称"
prop="algorithmName"
align="center"
></el-table-column>
<el-table-column label="算法报警等级" prop="alarmLevel" align="center">
<template slot-scope="scope">
<dict-tag
:options="dict.type.algorithm_level"
:value="scope.row.alarmLevel"
/>
</template>
</el-table-column>
<el-table-column label="推送负责人" prop="pushBy" align="center">
</el-table-column>
<el-table-column label="报警处理状态" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.row.alarmStatus === 2" type="warning"
>待处理</el-tag
>
<el-tag v-if="scope.row.alarmStatus === 1" type="success"
>正确报警</el-tag
>
<el-tag v-if="scope.row.alarmStatus === 0" type="danger"
>错误报警</el-tag
>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200px">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="pushBut(scope.row)">
报警推送
</el-button>
<el-button
type="text"
size="mini"
@click="deleteBut(scope.row.logId)"
>
<i class="el-icon-delete el-icon--left"></i>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 新增弹窗组件 -->
<el-dialog
title="推送用户选择"
:visible.sync="pushDialogVisible"
width="30%"
center
>
<el-select
v-model="selectedUsers"
filterable
placeholder="请选择要推送的用户"
style="width: 100%"
>
<el-option
v-for="user in userList"
:key="user.userId"
:label="user.userName"
:value="user.userId"
/>
</el-select>
<span slot="footer" class="dialog-footer">
<el-button @click="pushDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirmPush">确 定</el-button>
</span>
</el-dialog>
</div>
</div>
</template> </template>
<script> <script>
import {
getAlarmLogList,
delAlarmLog,
updateAlarmLog,
} from "@/api/business/alarmlog.js";
import { getCameraPositionList } from "@/api/business/cameraconfig.js";
import { listUser } from "@/api/system/user.js";
export default { export default {
name: "Alarmpush", dicts: ["algorithm_level"],
name: "Alarmlog",
props: {}, props: {},
components: {}, components: {},
data() { data() {
return {}; return {
form: {
id: 1, // 摄像头ID
videoAnlysisTasksName: "", // 视频分析任务名称
cameraName: "", // 摄像头名称
alarmImg: "", // 报警图片
alarmTime: "", // 报警时间
algorithmName: "", // 算法名称
algorithmLevel: null, // 算法报警等级
alarmStatus: null, // 报警处理状态
// 新增弹窗相关数据
pushDialogVisible: false,
selectedUsers: [],
userList: [], // 用户列表
currentLogId: null, // 当前操作的报警日志ID
},
tableList: [],
cameraAdderss: [
{
name: "摄像头1", // 摄像头名称
address: "地址一", // 摄像头地址
},
], // 摄像头地址
total: 0, // 总数量
queryParams: {
pageNum: 1, // 当前页码
pageSize: 10, // 每页显示的数量
},
// 新增弹窗相关数据
pushDialogVisible: false,
selectedUsers: [],
userList: [], // 用户列表
currentLogId: null, // 当前操作的报警日志ID
};
}, },
computed: {}, computed: {},
watch: {}, watch: {},
created() {}, created() {
this.getList();
getCameraPositionList().then((res) => {
if (res.code == 200) {
// 处理空数组情况
const rawData = Array.isArray(res.data) ? res.data : [];
this.cameraAdderss = this.flattenJson(rawData).map((item) => ({
positionName: item.positionName,
positionId: item.positionId,
}));
} else {
this.cameraAdderss = []; // 接口异常时设为空数组
}
});
},
mounted() {}, mounted() {},
methods: {}, methods: {
// 新增扁平化方法
flattenJson(arr) {
return arr.reduce((acc, item) => {
acc.push(item);
if (item.children && item.children.length) {
acc.push(...this.flattenJson(item.children));
}
return acc;
}, []);
},
// 搜索
search() {
let params = { ...this.queryParams, ...this.form };
getAlarmLogList(params).then((res) => {
if (res.code == 200) {
this.tableList = res.rows;
this.total = res.total;
}
});
},
// 删除
deleteBut(id) {
delAlarmLog(id).then((res) => {
if (res.code == 200) {
this.$message({
message: "删除成功",
type: "success",
});
this.getList();
} else {
this.$message({
message: res.msg,
type: "error",
});
}
});
},
// 编辑
pushBut(row, alarmStatus) {
this.currentLogId = row.logId;
this.pushDialogVisible = true;
this.loadUserList();
},
// 加载用户列表(需要对接后端API)
async loadUserList() {
try {
const response = await listUser();
if (response.code === 200) {
this.userList = response.rows;
}
} catch (error) {
console.error("获取用户列表失败:", error);
}
},
// 确认推送
async confirmPush() {
try {
const params = {
logId: this.currentLogId,
pushBy: this.selectedUsers,
};
const res = await updateAlarmLog(params);
if (res.code === 200) {
this.$message.success("推送成功");
this.pushDialogVisible = false;
this.selectedUsers = [];
this.getList(); // 刷新表格数据
}
} catch (error) {
this.$message.error("推送失败");
console.error("推送异常:", error);
}
},
// 获取数据列表
getList() {
getAlarmLogList({ ...this.queryParams, ...this.form }).then((res) => {
if (res.code == 200) {
this.tableList = res.rows;
this.total = res.total;
}
});
},
},
}; };
</script> </script>
......
This diff is collapsed.
<template>
<div class="app-container">
<el-row type="flex" :gutter="10">
<el-col :span="8">
<el-card
shadow="hover"
:body-style="{ padding: '20px', height: 'calc(100vh - 200px)' }"
>
<p slot="header"><b>报警弹窗及语音提醒</b></p>
<div>
<p>
选择开启提醒的算法 共
{{ selectedAlarmList.length }} 个算法开启提醒
</p>
<el-select
v-model="selectedAlarmList"
multiple
style="width: 100%"
placeholder="请选择"
>
<el-option
v-for="item in alarmOptions"
:key="item.algorithmId"
:label="item.algorithmName"
:value="item.algorithmId"
>
</el-option>
</el-select>
<p>提醒方式</p>
<p>
<span>系统报警语音</span>
<el-switch
v-model="systemAlarmAudio"
active-color="#13ce66"
inactive-color="#ff4949"
>
</el-switch>
</p>
<p>
<span>系统报警弹窗</span>
<el-switch
v-model="systemAlarmWidow"
active-color="#13ce66"
inactive-color="#ff4949"
>
</el-switch>
</p>
<p>显示方式</p>
</div>
</el-card>
</el-col>
<el-col :span="16">
<el-card
shadow="hover"
:body-style="{ padding: '20px', height: 'calc(100vh - 200px)' }"
>
<p slot="header"><b>提醒语音管理</b></p>
<el-table
:data="tableData"
style="width: 100%"
:row-style="{ textAlign: 'center' }"
>
<el-table-column
prop="algorithmId"
label="算法ID"
></el-table-column>
<el-table-column
prop="algorithmName"
label="算法名称"
></el-table-column>
<el-table-column label="算法等级">
<template slot-scope="scope">
<dict-tag
:options="dict.type.algorithm_level"
:value="scope.row.algorithmLevel"
/>
</template>
</el-table-column>
<el-table-column prop="alarmSoundName" label="报警语音">
<template slot-scope="scope">
{{ scope.row.alarmSoundName | SoundNamefilter }}
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
@click="handleEdit(scope.row)"
>
<i class="el-icon-video-play"></i>
试听
</el-button>
<el-upload
class="upload-demo"
:action="uploadImgUrl"
:on-success="handleUploadSuccess"
:multiple="false"
:show-file-list="false"
:headers="headers"
:limit="1"
>
<el-button
size="mini"
type="text"
@click="handleUpload(scope.row)"
>
<i class="el-icon-upload"></i>
替换/上传
</el-button>
<!-- <div slot="tip" class="el-upload__tip">
只能上传mp3/ACC/WAV/AMR文件,且不超过5MB
</div> -->
</el-upload>
<el-button
size="mini"
type="text"
@click="handleDelete(scope.row)"
>
<i class="el-icon-delete"></i>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import {
getAlgorithmList,
updateAlgorithm,
} from "@/api/business/algorithmconfig.js";
import { getToken } from "@/utils/auth";
export default {
dicts: ["algorithm_level"],
name: "Alarmreminder",
props: {},
components: {},
data() {
return {
tableData: [
{
id: "1",
algorithmName: "算法1",
algorithmLevel: 1,
alarmAudio: "audio1.mp3",
},
],
total: 0, // 总记录数
queryParams: {
pageNum: 1, // 当前页码
pageSize: 10, // 每页显示的记录数
},
selectedAlarmList: [], // 选中的报警算法列表
alarmOptions: [],
systemAlarmAudio: false, // 系统报警语音
systemAlarmWidow: false, // 系统报警弹窗
baseUrl: process.env.VUE_APP_BASE_API,
uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的文件服务器地址
headers: {
Authorization: "Bearer " + getToken(),
},
currentRow: null, // 新增当前操作行数据
};
},
computed: {},
watch: {},
created() {
this.getList();
},
mounted() {},
methods: {
//获取数据列表
getList() {
getAlgorithmList(this.queryParams).then((res) => {
if (res.code !== 200) {
return;
}
this.tableData = res.rows; // 假设接口返回的数据列表在res.data.list中
this.alarmOptions = res.rows;
this.selectedAlarmList = res.rows.map((item) => {
if (item.isSelect === 1) {
return item.algorithmId;
}
});
this.total = res.total; // 假设接口返回的总记录数在res.data.total中
});
},
//试听
handleEdit(row) {
const audio = new Audio(row.alarmSoundUrl); // 假设alarmAudio是音频文件的路径
audio.play(); // 播放音频
},
//删除
handleDelete(row) {
console.log("删除:", row);
},
//上传
handleUpload(row) {
this.currentRow = row; // 保存当前操作行
this.showUpload = true; // 显示上传组件
},
handleUploadSuccess(res) {
console.log(res, "上传成功");
// 上传成功回调处理
if (res.code === 200) {
updateAlgorithm({
algorithmId: this.currentRow.algorithmId, // 假设algorithmId是当前操作行的ID
alarmSoundUrl: res.url, // 假设res.url是上传成功后的音频文件URL
alarmSoundName: res.newFileName, // 假设res.fileName是上传成功后的音频文件名称
}).then((res) => {
if (res.code === 200) {
this.$message.success("上传成功");
this.showUpload = false; // 显示上传组件
this.getList(); // 刷新列表数据
} else {
this.$message.error(res.msg);
}
});
}
},
},
filters: {
SoundNamefilter(value) {
if (!value) return ""; // 如果值为空,返回空字符串
return value.split("_")[0]; // 返回文件名部分
},
},
};
</script>
<style scoped lang="scss">
.upload-demo {
display: inline-block;
}
</style>
...@@ -120,8 +120,6 @@ export default { ...@@ -120,8 +120,6 @@ export default {
}, },
mounted() {}, mounted() {},
methods: { methods: {
//编辑
tableEdit(row) {},
//获取列表 //获取列表
getList() { getList() {
getAlgorithmList(this.queryParams).then((res) => { getAlgorithmList(this.queryParams).then((res) => {
...@@ -146,7 +144,7 @@ export default { ...@@ -146,7 +144,7 @@ export default {
handleEditConfirm() { handleEditConfirm() {
this.$refs.form.validate((valid) => { this.$refs.form.validate((valid) => {
if (!valid) return; if (!valid) return;
updateAlgorithm(data).then((res) => { updateAlgorithm(this.form).then((res) => {
if (res.code === 200) { if (res.code === 200) {
this.$modal.msgSuccess("修改成功"); this.$modal.msgSuccess("修改成功");
this.getList(); this.getList();
......
...@@ -126,8 +126,8 @@ ...@@ -126,8 +126,8 @@
<template slot-scope="scope"> <template slot-scope="scope">
<image-preview <image-preview
:src="scope.row.cameraImage" :src="scope.row.cameraImage"
:width="100" :width="60"
:height="60" :height="40"
/> />
</template> </template>
</el-table-column> </el-table-column>
......
This diff is collapsed.
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="Pd20"> <div class="Pd20">
<el-row type="flex" :gutter="10"> <el-row type="flex" :gutter="10">
<el-col :span="6"> <el-col :span="6">
<el-button type="primary" plain size="mini" @click="drawer = true"> <el-button type="primary" plain size="mini" @click="append">
<i class="el-icon-plus el-icon--left"></i> <i class="el-icon-plus el-icon--left"></i>
创建视频分析任务 创建视频分析任务
</el-button> </el-button>
...@@ -216,6 +216,15 @@ export default { ...@@ -216,6 +216,15 @@ export default {
); );
}, },
}, },
// 在beforeDestroy生命周期添加
beforeDestroy() {
// 清理图表实例
this.chartInstance?.dispose();
// 释放图片资源
document.querySelectorAll(".content-top, .content-bottom").forEach((el) => {
el.style.backgroundImage = "none";
});
},
}; };
</script> </script>
...@@ -238,4 +247,11 @@ export default { ...@@ -238,4 +247,11 @@ export default {
::v-deep .el-drawer__header { ::v-deep .el-drawer__header {
margin-bottom: 0; margin-bottom: 0;
} }
.content-top {
background: url("../../assets/images/contenttop.png") no-repeat center;
will-change: transform; // 提示浏览器优化
}
.chart-box {
transform: translateZ(0);
}
</style> </style>
This diff is collapsed.
<template> <template>
<div :class="className" :style="{height:height,width:width}" /> <div :class="className" :style="{ height: height, width: width }" />
</template> </template>
<script> <script>
import * as echarts from 'echarts' import * as echarts from "echarts";
require('echarts/theme/macarons') // echarts theme require("echarts/theme/macarons"); // echarts theme
import resize from './mixins/resize' import resize from "./mixins/resize";
export default { export default {
mixins: [resize], mixins: [resize],
props: { props: {
className: { className: {
type: String, type: String,
default: 'chart' default: "chart",
}, },
width: { width: {
type: String, type: String,
default: '100%' default: "100%",
}, },
height: { height: {
type: String, type: String,
default: '350px' default: "350px",
}, },
autoResize: { autoResize: {
type: Boolean, type: Boolean,
default: true default: true,
}, },
chartData: { chartData: {
type: Object, type: Object,
required: true required: true,
} },
}, },
data() { data() {
return { return {
chart: null chart: null,
} };
}, },
watch: { watch: {
chartData: { chartData: {
deep: true, deep: true,
handler(val) { handler(val) {
this.setOptions(val) this.setOptions(val);
} },
} },
}, },
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
this.initChart() this.initChart();
}) });
}, },
beforeDestroy() { beforeDestroy() {
if (!this.chart) { if (!this.chart) {
return return;
} }
this.chart.dispose() this.chart.dispose();
this.chart = null this.chart = null;
}, },
methods: { methods: {
initChart() { initChart() {
this.chart = echarts.init(this.$el, 'macarons') this.chart = echarts.init(this.$el, "macarons");
this.setOptions(this.chartData) this.setOptions(this.chartData);
}, },
setOptions({ expectedData, actualData } = {}) { setOptions({ xdata, ydata } = {}) {
let list = ydata.map((item) => {
return {
name: item.name,
smooth: true,
type: "line",
itemStyle: {
normal: {
color: "#3888fa",
lineStyle: {
color: "#3888fa",
width: 2,
},
areaStyle: {
color: "#f3f8ff",
},
},
},
data: item.data,
animationDuration: 2800,
animationEasing: "quadraticOut",
};
});
let lendata = ydata.map((item) => {
return item.name;
});
this.chart.setOption({ this.chart.setOption({
xAxis: { xAxis: {
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], data: xdata,
boundaryGap: false, boundaryGap: false,
axisTick: { axisTick: {
show: false show: false,
} },
}, },
grid: { grid: {
left: 10, left: 10,
right: 10, right: 40,
bottom: 20, bottom: 20,
top: 30, top: 30,
containLabel: true containLabel: true,
}, },
tooltip: { tooltip: {
trigger: 'axis', trigger: "axis",
axisPointer: { axisPointer: {
type: 'cross' type: "cross",
}, },
padding: [5, 10] padding: [5, 10],
}, },
yAxis: { yAxis: {
axisTick: { axisTick: {
show: false show: false,
} },
}, },
legend: { legend: {
data: ['expected', 'actual'] data: lendata,
}, },
series: [{ series: list,
name: 'expected', itemStyle: { });
normal: { },
color: '#FF005A', },
lineStyle: { };
color: '#FF005A',
width: 2
}
}
},
smooth: true,
type: 'line',
data: expectedData,
animationDuration: 2800,
animationEasing: 'cubicInOut'
},
{
name: 'actual',
smooth: true,
type: 'line',
itemStyle: {
normal: {
color: '#3888fa',
lineStyle: {
color: '#3888fa',
width: 2
},
areaStyle: {
color: '#f3f8ff'
}
}
},
data: actualData,
animationDuration: 2800,
animationEasing: 'quadraticOut'
}]
})
}
}
}
</script> </script>
<template>
<div :class="className" :style="{ height: height, width: width }" />
</template>
<script>
import * as echarts from "echarts";
require("echarts/theme/macarons"); // echarts theme
import resize from "./mixins/resize";
// 柔和颜色随机
const getRandomColor = () => {
// 定义艳丽颜色的RGB范围
const min = 0;
const max = 255;
// 生成三个在指定范围内的随机数,分别代表R、G、B
const r = Math.floor(Math.random() * (max - min + 1)) + min;
const g = Math.floor(Math.random() * (max - min + 1)) + min;
const b = Math.floor(Math.random() * (max - min + 1)) + min;
// 为了确保颜色清新,适当调整色差
const adjustColor = (color) => {
if (Math.random() > 0.5) {
return Math.min(255, color + Math.floor((255 - color) * 0.2));
} else {
return Math.max(0, color - Math.floor(color * 0.2));
}
};
const adjustedR = adjustColor(r);
const adjustedG = adjustColor(g);
const adjustedB = adjustColor(b);
// 确保颜色组合舒适自然
const ensureComfortable = (r, g, b) => {
// 计算颜色的亮度
const brightness = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
// 如果亮度太低或太高,调整其中一个颜色分量
if (brightness < 0.3) {
return [Math.min(255, r + 50), g, b];
} else if (brightness > 0.7) {
return [Math.max(0, r - 50), g, b];
}
return [r, g, b];
};
const [finalR, finalG, finalB] = ensureComfortable(
adjustedR,
adjustedG,
adjustedB
);
// 将RGB值转换为16进制字符串
const hexR = finalR.toString(16).padStart(2, "0");
const hexG = finalG.toString(16).padStart(2, "0");
const hexB = finalB.toString(16).padStart(2, "0");
// 返回带有#前缀的十六进制颜色字符串
return `#${hexR}${hexG}${hexB}`;
};
export default {
mixins: [resize],
props: {
className: {
type: String,
default: "chart",
},
width: {
type: String,
default: "100%",
},
height: {
type: String,
default: "350px",
},
autoResize: {
type: Boolean,
default: true,
},
chartData: {
type: Object,
required: true,
},
},
data() {
return {
chart: null,
};
},
watch: {
chartData: {
deep: true,
handler(val) {
this.setOptions(val);
},
},
},
mounted() {
this.$nextTick(() => {
this.initChart();
});
},
beforeDestroy() {
if (!this.chart) {
return;
}
this.chart.dispose();
this.chart = null;
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, "macarons");
this.setOptions(this.chartData);
},
setOptions({ xdata, ydata } = {}) {
let list = ydata.map((item) => {
return {
name: item.name,
smooth: true,
type: "line",
itemStyle: {
normal: {
color: getRandomColor(), // 修改这里使用随机颜色
lineStyle: {
color: getRandomColor(), // 修改这里使用随机颜色
width: 2,
},
areaStyle: {
color: "#f3f8ff",
},
},
},
data: item.data,
animationDuration: 2800,
animationEasing: "quadraticOut",
};
});
let lendata = ydata.map((item) => {
return item.name;
});
this.chart.setOption({
xAxis: {
data: xdata,
boundaryGap: false,
axisTick: {
show: false,
},
},
grid: {
left: 10,
right: 40,
bottom: 20,
top: 30,
containLabel: true,
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "cross",
},
padding: [5, 10],
},
yAxis: {
axisTick: {
show: false,
},
},
legend: {
data: lendata,
},
series: list,
});
},
},
};
</script>
...@@ -22,6 +22,31 @@ export default { ...@@ -22,6 +22,31 @@ export default {
type: String, type: String,
default: "300px", default: "300px",
}, },
pieData: {
type: Array,
default: function () {
return [
{ value: 0, name: "已启动" },
{ value: 0, name: "未启动" },
];
},
},
},
watch: {
pieData: {
handler(newVal, oldVal) {
if (this.chart) {
this.chart.setOption({
series: [
{
data: newVal,
},
],
});
}
},
deep: true,
},
}, },
data() { data() {
return { return {
...@@ -61,11 +86,8 @@ export default { ...@@ -61,11 +86,8 @@ export default {
type: "pie", type: "pie",
radius: ["45%", "75%"], radius: ["45%", "75%"],
center: ["50%", "55%"], center: ["50%", "55%"],
color: ["#67C23A", "#F56C6C"], // 新增颜色数组配置 color: ["#F56C6C", "#67C23A"], // 新增颜色数组配置
data: [ data: this.pieData,
{ value: 320, name: "已启动" },
{ value: 240, name: "未启动" },
],
avoidLabelOverlap: false, avoidLabelOverlap: false,
itemStyle: { itemStyle: {
borderRadius: 10, borderRadius: 10,
......
<template>
<div :class="className" :style="{ height: height, width: width }" />
</template>
<script>
import * as echarts from "echarts";
require("echarts/theme/macarons"); // echarts theme
import resize from "./mixins/resize";
export default {
mixins: [resize],
props: {
className: {
type: String,
default: "chart",
},
width: {
type: String,
default: "100%",
},
height: {
type: String,
default: "300px",
},
pieData: {
type: Array,
default: function () {
return [
{ value: 0, name: "已启动" },
{ value: 0, name: "未启动" },
];
},
},
},
watch: {
pieData: {
handler(newVal, oldVal) {
if (this.chart) {
this.chart.setOption({
series: [
{
data: newVal,
},
],
});
}
},
deep: true,
},
},
data() {
return {
chart: null,
};
},
mounted() {
this.$nextTick(() => {
this.initChart();
});
},
beforeDestroy() {
if (!this.chart) {
return;
}
this.chart.dispose();
this.chart = null;
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, "macarons");
this.chart.setOption({
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)",
},
legend: {
left: "center",
bottom: "1%",
data: ["已启动", "未启动"],
textStyle: {
color: "#fff", // 设置图例文字颜色为白色
},
},
series: [
{
name: "监控摄像头",
type: "pie",
color: ["#c9cee2", "#184ff0"], // 新增颜色数组配置
data: this.pieData,
avoidLabelOverlap: false,
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: "bold",
},
},
label: {
position: "center",
},
},
],
});
},
},
};
</script>
This diff is collapsed.
<template> <template>
<div class="login"> <div class="login">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form"> <el-form
<h3 class="title">{{title}}</h3> ref="loginForm"
:model="loginForm"
:rules="loginRules"
class="login-form"
>
<h3 class="title">{{ title }}</h3>
<el-form-item prop="username"> <el-form-item prop="username">
<el-input <el-input
v-model="loginForm.username" v-model="loginForm.username"
...@@ -9,7 +14,11 @@ ...@@ -9,7 +14,11 @@
auto-complete="off" auto-complete="off"
placeholder="账号" placeholder="账号"
> >
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" /> <svg-icon
slot="prefix"
icon-class="user"
class="el-input__icon input-icon"
/>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
...@@ -20,7 +29,11 @@ ...@@ -20,7 +29,11 @@
placeholder="密码" placeholder="密码"
@keyup.enter.native="handleLogin" @keyup.enter.native="handleLogin"
> >
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" /> <svg-icon
slot="prefix"
icon-class="password"
class="el-input__icon input-icon"
/>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="code" v-if="captchaEnabled"> <el-form-item prop="code" v-if="captchaEnabled">
...@@ -31,32 +44,42 @@ ...@@ -31,32 +44,42 @@
style="width: 63%" style="width: 63%"
@keyup.enter.native="handleLogin" @keyup.enter.native="handleLogin"
> >
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" /> <svg-icon
slot="prefix"
icon-class="validCode"
class="el-input__icon input-icon"
/>
</el-input> </el-input>
<div class="login-code"> <div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/> <img :src="codeUrl" @click="getCode" class="login-code-img" />
</div> </div>
</el-form-item> </el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox> <el-checkbox
<el-form-item style="width:100%;"> v-model="loginForm.rememberMe"
style="margin: 0px 0px 25px 0px"
>记住密码</el-checkbox
>
<el-form-item style="width: 100%">
<el-button <el-button
:loading="loading" :loading="loading"
size="medium" size="medium"
type="primary" type="primary"
style="width:100%;" style="width: 100%"
@click.native.prevent="handleLogin" @click.native.prevent="handleLogin"
> >
<span v-if="!loading">登 录</span> <span v-if="!loading">登 录</span>
<span v-else>登 录 中...</span> <span v-else>登 录 中...</span>
</el-button> </el-button>
<div style="float: right;" v-if="register"> <div style="float: right" v-if="register">
<router-link class="link-type" :to="'/register'">立即注册</router-link> <router-link class="link-type" :to="'/register'"
>立即注册</router-link
>
</div> </div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 底部 --> <!-- 底部 -->
<div class="el-login-footer"> <div class="el-login-footer">
<span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span> <span></span>
</div> </div>
</div> </div>
</template> </template>
...@@ -64,7 +87,7 @@ ...@@ -64,7 +87,7 @@
<script> <script>
import { getCodeImg } from "@/api/login"; import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt' import { encrypt, decrypt } from "@/utils/jsencrypt";
export default { export default {
name: "Login", name: "Login",
...@@ -77,32 +100,32 @@ export default { ...@@ -77,32 +100,32 @@ export default {
password: "admin123", password: "admin123",
rememberMe: false, rememberMe: false,
code: "", code: "",
uuid: "" uuid: "",
}, },
loginRules: { loginRules: {
username: [ username: [
{ required: true, trigger: "blur", message: "请输入您的账号" } { required: true, trigger: "blur", message: "请输入您的账号" },
], ],
password: [ password: [
{ required: true, trigger: "blur", message: "请输入您的密码" } { required: true, trigger: "blur", message: "请输入您的密码" },
], ],
code: [{ required: true, trigger: "change", message: "请输入验证码" }] code: [{ required: true, trigger: "change", message: "请输入验证码" }],
}, },
loading: false, loading: false,
// 验证码开关 // 验证码开关
captchaEnabled: true, captchaEnabled: true,
// 注册开关 // 注册开关
register: false, register: false,
redirect: undefined redirect: undefined,
}; };
}, },
watch: { watch: {
$route: { $route: {
handler: function(route) { handler: function (route) {
this.redirect = route.query && route.query.redirect; this.redirect = route.query && route.query.redirect;
}, },
immediate: true immediate: true,
} },
}, },
created() { created() {
this.getCode(); this.getCode();
...@@ -110,8 +133,9 @@ export default { ...@@ -110,8 +133,9 @@ export default {
}, },
methods: { methods: {
getCode() { getCode() {
getCodeImg().then(res => { getCodeImg().then((res) => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled; this.captchaEnabled =
res.captchaEnabled === undefined ? true : res.captchaEnabled;
if (this.captchaEnabled) { if (this.captchaEnabled) {
this.codeUrl = "data:image/gif;base64," + res.img; this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid; this.loginForm.uuid = res.uuid;
...@@ -121,38 +145,46 @@ export default { ...@@ -121,38 +145,46 @@ export default {
getCookie() { getCookie() {
const username = Cookies.get("username"); const username = Cookies.get("username");
const password = Cookies.get("password"); const password = Cookies.get("password");
const rememberMe = Cookies.get('rememberMe') const rememberMe = Cookies.get("rememberMe");
this.loginForm = { this.loginForm = {
username: username === undefined ? this.loginForm.username : username, username: username === undefined ? this.loginForm.username : username,
password: password === undefined ? this.loginForm.password : decrypt(password), password:
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe) password === undefined ? this.loginForm.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
}; };
}, },
handleLogin() { handleLogin() {
this.$refs.loginForm.validate(valid => { this.$refs.loginForm.validate((valid) => {
if (valid) { if (valid) {
this.loading = true; this.loading = true;
if (this.loginForm.rememberMe) { if (this.loginForm.rememberMe) {
Cookies.set("username", this.loginForm.username, { expires: 30 }); Cookies.set("username", this.loginForm.username, { expires: 30 });
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 }); Cookies.set("password", encrypt(this.loginForm.password), {
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 }); expires: 30,
});
Cookies.set("rememberMe", this.loginForm.rememberMe, {
expires: 30,
});
} else { } else {
Cookies.remove("username"); Cookies.remove("username");
Cookies.remove("password"); Cookies.remove("password");
Cookies.remove('rememberMe'); Cookies.remove("rememberMe");
} }
this.$store.dispatch("Login", this.loginForm).then(() => { this.$store
this.$router.push({ path: this.redirect || "/" }).catch(()=>{}); .dispatch("Login", this.loginForm)
}).catch(() => { .then(() => {
this.loading = false; this.$router.push({ path: this.redirect || "/" }).catch(() => {});
if (this.captchaEnabled) { })
this.getCode(); .catch(() => {
} this.loading = false;
}); if (this.captchaEnabled) {
this.getCode();
}
});
} }
}); });
} },
} },
}; };
</script> </script>
...@@ -162,8 +194,11 @@ export default { ...@@ -162,8 +194,11 @@ export default {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100%; height: 100%;
background-image: url("../assets/images/login-background.jpg"); background: url("../assets/images/login-bg.png") no-repeat;
background-size: cover; background-size: cover;
image-rendering: -webkit-optimize-contrast; // 优化渲染
backface-visibility: hidden; // 开启硬件加速
transform: translateZ(0);
} }
.title { .title {
margin: 0px auto 30px auto; margin: 0px auto 30px auto;
......
...@@ -45,6 +45,14 @@ module.exports = { ...@@ -45,6 +45,14 @@ module.exports = {
pathRewrite: { pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: '' ['^' + process.env.VUE_APP_BASE_API]: ''
} }
},
'/proxy-iframe': {
target: 'http://192.168.2.22:5000', // 替换为实际目标地址
changeOrigin: true,
pathRewrite: {
'^/proxy-iframe': '' // 替换为实际路径前缀
}
} }
}, },
disableHostCheck: true disableHostCheck: true
......
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