Commit 2cf53cfe authored by lei's avatar lei

add:

parent d175353f
......@@ -7,5 +7,7 @@ ENV = 'development'
# 若依管理系统/开发环境
VUE_APP_BASE_API = '/dev-api'
VUE_APP_WS_URL = 'ws://192.168.2.16:8081'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true
......@@ -6,3 +6,4 @@ ENV = 'production'
# 若依管理系统/生产环境
VUE_APP_BASE_API = '/prod-api'
VUE_APP_WS_URL = '/ws-api'
......@@ -21,3 +21,4 @@ selenium-debug.log
package-lock.json
yarn.lock
public/video/webrtc-streamer.exe
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
/>
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= webpackConfig.name %></title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<!-- <script
type="text/javascript"
src="<%= BASE_URL %>video/webrtcstreamer.js"
></script>
<script
type="text/javascript"
src="<%= BASE_URL %>video/adapter.min.js"
></script> -->
<!--[if lt IE 11
]><script>
window.location.href = "/html/ie.html";
</script><!
[endif]-->
<style>
html,
body,
......@@ -42,7 +57,7 @@
margin: -75px 0 0 -75px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
border-top-color: #fff;
-webkit-animation: spin 2s linear infinite;
-ms-animation: spin 2s linear infinite;
-moz-animation: spin 2s linear infinite;
......@@ -60,7 +75,7 @@
bottom: 5px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
border-top-color: #fff;
-webkit-animation: spin 3s linear infinite;
-moz-animation: spin 3s linear infinite;
-o-animation: spin 3s linear infinite;
......@@ -77,7 +92,7 @@
bottom: 15px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
border-top-color: #fff;
-moz-animation: spin 1.5s linear infinite;
-o-animation: spin 1.5s linear infinite;
-ms-animation: spin 1.5s linear infinite;
......@@ -85,7 +100,6 @@
animation: spin 1.5s linear infinite;
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
......@@ -112,13 +126,12 @@
}
}
#loader-wrapper .loader-section {
position: fixed;
top: 0;
width: 51%;
height: 100%;
background: #7171C6;
background: #7171c6;
z-index: 1000;
-webkit-transform: translateX(0);
-ms-transform: translateX(0);
......@@ -133,21 +146,20 @@
right: 0;
}
.loaded #loader-wrapper .loader-section.section-left {
-webkit-transform: translateX(-100%);
-ms-transform: translateX(-100%);
transform: translateX(-100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.loaded #loader-wrapper .loader-section.section-right {
-webkit-transform: translateX(100%);
-ms-transform: translateX(100%);
transform: translateX(100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.loaded #loader {
......@@ -174,8 +186,8 @@
}
#loader-wrapper .load_title {
font-family: 'Open Sans';
color: #FFF;
font-family: "Open Sans";
color: #fff;
font-size: 19px;
width: 100%;
text-align: center;
......@@ -190,11 +202,12 @@
font-weight: normal;
font-style: italic;
font-size: 13px;
color: #FFF;
color: #fff;
opacity: 0.5;
}
</style>
</head>
<body>
<div id="app">
<div id="loader-wrapper">
......
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 @@
<script>
import ThemePicker from "@/components/ThemePicker";
import MessageBox from "@/components/MessageBox/index.vue";
export default {
name: "App",
components: { ThemePicker },
components: { ThemePicker, MessageBox },
data() {
return {
socket: null, // WebSocket实例
baseWsUrl: process.env.VUE_APP_WS_URL, // WebSocket服务器地址
};
},
metaInfo() {
return {
title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title,
titleTemplate: title => {
return title ? `${title} - ${process.env.VUE_APP_TITLE}` : process.env.VUE_APP_TITLE
title:
this.$store.state.settings.dynamicTitle &&
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>
<style scoped>
......
......@@ -33,3 +33,16 @@ export function delAlgorithm(id) {
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
......@@ -32,3 +32,11 @@ export function delVideoAnalysisTask(id) {
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 {
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Arial, sans-serif;
}
.el-radio__original {
display: none !important; /* 隐藏原生 radio 输入,但仍然允许交互 */
}
label {
font-weight: 700;
}
......
......@@ -63,7 +63,7 @@ export default {
data: {
type: Object,
},
// 图片数量限制
// 文件数量限制
limit: {
type: Number,
default: 1,
......@@ -92,7 +92,7 @@ export default {
dialogVisible: false,
hideUpload: false,
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: {
Authorization: "Bearer " + getToken(),
},
......@@ -151,7 +151,7 @@ export default {
if (!isImg) {
this.$modal.msgError(
`文件格式不正确,请上传${this.fileType.join("/")}图片格式文件!`
`文件格式不正确,请上传${this.fileType.join("/")}文件格式文件!`
);
return false;
}
......@@ -162,11 +162,11 @@ export default {
if (this.fileSize) {
const isLt = file.size / 1024 / 1024 < this.fileSize;
if (!isLt) {
this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`);
this.$modal.msgError(`上传头像文件大小不能超过 ${this.fileSize} MB!`);
return false;
}
}
this.$modal.loading("正在上传图片,请稍候...");
this.$modal.loading("正在上传文件,请稍候...");
this.number++;
},
// 文件个数超出
......@@ -186,7 +186,7 @@ export default {
this.uploadedSuccessfully();
}
},
// 删除图片
// 删除文件
handleDelete(file) {
const findex = this.fileList.map((f) => f.name).indexOf(file.name);
if (findex > -1) {
......@@ -196,7 +196,7 @@ export default {
},
// 上传失败
handleUploadError() {
this.$modal.msgError("上传图片失败,请重试");
this.$modal.msgError("上传文件失败,请重试");
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'
import directive from './directive' // directive
import plugins from './plugins' // plugins
import { download } from '@/utils/request'
import './assets/icons' // icon
import './permission' // permission control
import { getDicts } from "@/api/system/dict/data";
......@@ -37,6 +36,8 @@ import DictTag from '@/components/DictTag'
import VueMeta from 'vue-meta'
// 字典数据组件
import DictData from '@/components/DictData'
//自适应组件
import VScaleScreen from 'v-scale-screen'
// 全局方法挂载
Vue.prototype.getDicts = getDicts
......@@ -61,6 +62,7 @@ Vue.component('ImagePreview', ImagePreview)
Vue.use(directive)
Vue.use(plugins)
Vue.use(VueMeta)
Vue.use(VScaleScreen)
DictData.install()
/**
......
......@@ -7,8 +7,8 @@
<el-option
v-for="(item, index) in cameraAdderss"
:key="index"
:value="item.address"
:label="item.name"
:value="item.positionId"
:label="item.positionName"
></el-option>
</el-select>
</el-form-item>
......@@ -32,7 +32,7 @@
placeholder="报警处理状态"
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="0" label="错误报警"></el-option>
</el-select>
......@@ -58,7 +58,7 @@
></el-table-column>
<el-table-column
label="视频分析任务名称"
prop="videoAnlysisTasksName"
prop="taskName"
align="center"
></el-table-column>
<el-table-column
......@@ -66,11 +66,15 @@
prop="cameraName"
align="center"
></el-table-column>
<el-table-column
label="报警图片"
prop="alarmImg"
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"
......@@ -81,32 +85,40 @@
prop="algorithmName"
align="center"
></el-table-column>
<el-table-column
label="算法报警等级"
prop="algorithmLevel"
align="center"
>
<el-table-column label="算法报警等级" prop="alarmLevel" align="center">
<template slot-scope="scope">
<dict-tag
:options="dict.type.algorithm_level"
:value="scope.row.algorithmLevel"
:value="scope.row.alarmLevel"
/>
</template>
</el-table-column>
<el-table-column label="报警处理状态" align="center">
<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>
</el-table-column>
<el-table-column label="操作" align="center" width="200px">
<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 type="text" size="mini" @click="editBut(scope.row)">
<el-button type="text" size="mini" @click="editBut(scope.row, 0)">
错误报警
</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>
删除
</el-button>
......@@ -130,6 +142,7 @@ import {
delAlarmLog,
updateAlarmLog,
} from "@/api/business/alarmlog.js";
import { getCameraPositionList } from "@/api/business/cameraconfig.js";
export default {
dicts: ["algorithm_level"],
name: "Alarmlog",
......@@ -144,8 +157,8 @@ export default {
alarmImg: "", // 报警图片
alarmTime: "", // 报警时间
algorithmName: "", // 算法名称
algorithmLevel: 1, // 算法报警等级
alarmStatus: 0, // 报警处理状态
algorithmLevel: null, // 算法报警等级
alarmStatus: null, // 报警处理状态
},
tableList: [],
cameraAdderss: [
......@@ -166,24 +179,81 @@ export default {
watch: {},
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() {},
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) {
this.$confirm("确认删除吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
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",
});
}
});
},
// 编辑
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() {
getAlarmLogList(this.queryParams).then((res) => {
getAlarmLogList({ ...this.queryParams, ...this.form }).then((res) => {
if (res.code == 200) {
this.tableList = res.rows;
this.total = res.total;
......
<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>
<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 {
name: "Alarmpush",
dicts: ["algorithm_level"],
name: "Alarmlog",
props: {},
components: {},
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: {},
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() {},
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>
......
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 {
},
mounted() {},
methods: {
//编辑
tableEdit(row) {},
//获取列表
getList() {
getAlgorithmList(this.queryParams).then((res) => {
......@@ -146,7 +144,7 @@ export default {
handleEditConfirm() {
this.$refs.form.validate((valid) => {
if (!valid) return;
updateAlgorithm(data).then((res) => {
updateAlgorithm(this.form).then((res) => {
if (res.code === 200) {
this.$modal.msgSuccess("修改成功");
this.getList();
......
......@@ -126,8 +126,8 @@
<template slot-scope="scope">
<image-preview
:src="scope.row.cameraImage"
:width="100"
:height="60"
:width="60"
:height="40"
/>
</template>
</el-table-column>
......
This diff is collapsed.
......@@ -3,7 +3,7 @@
<div class="Pd20">
<el-row type="flex" :gutter="10">
<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>
创建视频分析任务
</el-button>
......@@ -216,6 +216,15 @@ export default {
);
},
},
// 在beforeDestroy生命周期添加
beforeDestroy() {
// 清理图表实例
this.chartInstance?.dispose();
// 释放图片资源
document.querySelectorAll(".content-top, .content-bottom").forEach((el) => {
el.style.backgroundImage = "none";
});
},
};
</script>
......@@ -238,4 +247,11 @@ export default {
::v-deep .el-drawer__header {
margin-bottom: 0;
}
.content-top {
background: url("../../assets/images/contenttop.png") no-repeat center;
will-change: transform; // 提示浏览器优化
}
.chart-box {
transform: translateZ(0);
}
</style>
This diff is collapsed.
<template>
<div :class="className" :style="{height:height,width:width}" />
<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'
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'
default: "chart",
},
width: {
type: String,
default: '100%'
default: "100%",
},
height: {
type: String,
default: '350px'
default: "350px",
},
autoResize: {
type: Boolean,
default: true
default: true,
},
chartData: {
type: Object,
required: true
}
required: true,
},
},
data() {
return {
chart: null
}
chart: null,
};
},
watch: {
chartData: {
deep: true,
handler(val) {
this.setOptions(val)
}
}
this.setOptions(val);
},
},
},
mounted() {
this.$nextTick(() => {
this.initChart()
})
this.initChart();
});
},
beforeDestroy() {
if (!this.chart) {
return
return;
}
this.chart.dispose()
this.chart = null
this.chart.dispose();
this.chart = null;
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.setOptions(this.chartData)
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: "#3888fa",
lineStyle: {
color: "#3888fa",
width: 2,
},
areaStyle: {
color: "#f3f8ff",
},
setOptions({ expectedData, actualData } = {}) {
},
},
data: item.data,
animationDuration: 2800,
animationEasing: "quadraticOut",
};
});
let lendata = ydata.map((item) => {
return item.name;
});
this.chart.setOption({
xAxis: {
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
data: xdata,
boundaryGap: false,
axisTick: {
show: false
}
show: false,
},
},
grid: {
left: 10,
right: 10,
right: 40,
bottom: 20,
top: 30,
containLabel: true
containLabel: true,
},
tooltip: {
trigger: 'axis',
trigger: "axis",
axisPointer: {
type: 'cross'
type: "cross",
},
padding: [5, 10]
padding: [5, 10],
},
yAxis: {
axisTick: {
show: false
}
},
legend: {
data: ['expected', 'actual']
show: false,
},
series: [{
name: 'expected', itemStyle: {
normal: {
color: '#FF005A',
lineStyle: {
color: '#FF005A',
width: 2
}
}
},
smooth: true,
type: 'line',
data: expectedData,
animationDuration: 2800,
animationEasing: 'cubicInOut'
legend: {
data: lendata,
},
{
name: 'actual',
smooth: true,
type: 'line',
itemStyle: {
normal: {
color: '#3888fa',
lineStyle: {
color: '#3888fa',
width: 2
series: list,
});
},
areaStyle: {
color: '#f3f8ff'
}
}
},
data: actualData,
animationDuration: 2800,
animationEasing: 'quadraticOut'
}]
})
}
}
}
};
</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 {
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 {
......@@ -61,11 +86,8 @@ export default {
type: "pie",
radius: ["45%", "75%"],
center: ["50%", "55%"],
color: ["#67C23A", "#F56C6C"], // 新增颜色数组配置
data: [
{ value: 320, name: "已启动" },
{ value: 240, name: "未启动" },
],
color: ["#F56C6C", "#67C23A"], // 新增颜色数组配置
data: this.pieData,
avoidLabelOverlap: false,
itemStyle: {
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>
<div class="login">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">{{title}}</h3>
<el-form
ref="loginForm"
:model="loginForm"
:rules="loginRules"
class="login-form"
>
<h3 class="title">{{ title }}</h3>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
......@@ -9,7 +14,11 @@
auto-complete="off"
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-form-item>
<el-form-item prop="password">
......@@ -20,7 +29,11 @@
placeholder="密码"
@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-form-item>
<el-form-item prop="code" v-if="captchaEnabled">
......@@ -31,32 +44,42 @@
style="width: 63%"
@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>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
<img :src="codeUrl" @click="getCode" class="login-code-img" />
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
<el-form-item style="width:100%;">
<el-checkbox
v-model="loginForm.rememberMe"
style="margin: 0px 0px 25px 0px"
>记住密码</el-checkbox
>
<el-form-item style="width: 100%">
<el-button
:loading="loading"
size="medium"
type="primary"
style="width:100%;"
style="width: 100%"
@click.native.prevent="handleLogin"
>
<span v-if="!loading">登 录</span>
<span v-else>登 录 中...</span>
</el-button>
<div style="float: right;" v-if="register">
<router-link class="link-type" :to="'/register'">立即注册</router-link>
<div style="float: right" v-if="register">
<router-link class="link-type" :to="'/register'"
>立即注册</router-link
>
</div>
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span>
<span></span>
</div>
</div>
</template>
......@@ -64,7 +87,7 @@
<script>
import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt'
import { encrypt, decrypt } from "@/utils/jsencrypt";
export default {
name: "Login",
......@@ -77,32 +100,32 @@ export default {
password: "admin123",
rememberMe: false,
code: "",
uuid: ""
uuid: "",
},
loginRules: {
username: [
{ required: true, trigger: "blur", message: "请输入您的账号" }
{ required: true, trigger: "blur", message: "请输入您的账号" },
],
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,
// 验证码开关
captchaEnabled: true,
// 注册开关
register: false,
redirect: undefined
redirect: undefined,
};
},
watch: {
$route: {
handler: function(route) {
handler: function (route) {
this.redirect = route.query && route.query.redirect;
},
immediate: true
}
immediate: true,
},
},
created() {
this.getCode();
......@@ -110,8 +133,9 @@ export default {
},
methods: {
getCode() {
getCodeImg().then(res => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
getCodeImg().then((res) => {
this.captchaEnabled =
res.captchaEnabled === undefined ? true : res.captchaEnabled;
if (this.captchaEnabled) {
this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid;
......@@ -121,29 +145,37 @@ export default {
getCookie() {
const username = Cookies.get("username");
const password = Cookies.get("password");
const rememberMe = Cookies.get('rememberMe')
const rememberMe = Cookies.get("rememberMe");
this.loginForm = {
username: username === undefined ? this.loginForm.username : username,
password: password === undefined ? this.loginForm.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
password:
password === undefined ? this.loginForm.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
};
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.loading = true;
if (this.loginForm.rememberMe) {
Cookies.set("username", this.loginForm.username, { expires: 30 });
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
Cookies.set("password", encrypt(this.loginForm.password), {
expires: 30,
});
Cookies.set("rememberMe", this.loginForm.rememberMe, {
expires: 30,
});
} else {
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove('rememberMe');
Cookies.remove("rememberMe");
}
this.$store.dispatch("Login", this.loginForm).then(() => {
this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
}).catch(() => {
this.$store
.dispatch("Login", this.loginForm)
.then(() => {
this.$router.push({ path: this.redirect || "/" }).catch(() => {});
})
.catch(() => {
this.loading = false;
if (this.captchaEnabled) {
this.getCode();
......@@ -151,8 +183,8 @@ export default {
});
}
});
}
}
},
},
};
</script>
......@@ -162,8 +194,11 @@ export default {
justify-content: center;
align-items: center;
height: 100%;
background-image: url("../assets/images/login-background.jpg");
background: url("../assets/images/login-bg.png") no-repeat;
background-size: cover;
image-rendering: -webkit-optimize-contrast; // 优化渲染
backface-visibility: hidden; // 开启硬件加速
transform: translateZ(0);
}
.title {
margin: 0px auto 30px auto;
......
......@@ -45,6 +45,14 @@ module.exports = {
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
},
'/proxy-iframe': {
target: 'http://192.168.2.22:5000', // 替换为实际目标地址
changeOrigin: true,
pathRewrite: {
'^/proxy-iframe': '' // 替换为实际路径前缀
}
}
},
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