@@ -17,7 +17,7 @@ import { ReqstartId, ResPage, sysCamera, ReqstopId } from "@/api/interface"; | |||
const http = moduleRequest("/business/deviceApi/"); | |||
const http2 = moduleRequest("/business/cameraInfo/"); | |||
const http3 = moduleRequest("/business/cameraGroup/"); | |||
const http4 = moduleRequest("/business/warn/"); | |||
/** | |||
* @Description: 单页管理 | |||
* @Author: wangwenpei | |||
@@ -56,6 +56,14 @@ const monitorLIVEApi = { | |||
setWarningPushPerson(params: sysCamera.setGroup) { | |||
return http2.post<ResPage<sysCamera.MonitorInfo>>("batchSetPushPersonByWarn", params); | |||
}, | |||
/* 设置推送人 */ | |||
setPushPersonByWarn(params: sysCamera.setGroup) { | |||
return http4.post<ResPage<sysCamera.MonitorInfo>>("setPushPersonByWarn", params); | |||
}, | |||
// 数据同步 | |||
setdat() { | |||
return http2.post<any>("dat"); | |||
}, | |||
// 获取摄像头分组树 | |||
groupList(params: sysCamera.Tree) { | |||
return http3.get<ResPage<sysCamera.MonitorInfo>>("getNoPageList"); | |||
@@ -39,6 +39,10 @@ const abilityApi = { | |||
return http1.download("reportExport", params, { | |||
showHeader: true | |||
}); | |||
}, | |||
// 获取订阅配置 | |||
getFuncConf(params: any) { | |||
return http.get("getFuncConf", params); | |||
} | |||
}; | |||
@@ -10,7 +10,17 @@ | |||
<template v-for="slotKey in slotKeys" #[slotKey]> <slot :name="slotKey" /></template> | |||
</el-drawer> | |||
<!-- 对话框 --> | |||
<el-dialog v-else top="50px" :visible="visible" :destroy-on-close="true" draggable v-bind="$attrs" @close="close" :width="formProps.formSize"> | |||
<el-dialog | |||
v-else | |||
top="50px" | |||
:visible="visible" | |||
:close-on-click-modal="formProps.closeOnClickModal" | |||
:destroy-on-close="true" | |||
draggable | |||
v-bind="$attrs" | |||
@close="close" | |||
:width="formProps.formSize" | |||
> | |||
<template v-for="slotKey in slotKeys" #[slotKey]> <slot :name="slotKey" /></template> | |||
</el-dialog> | |||
</div> | |||
@@ -29,6 +39,10 @@ const formProps = defineProps({ | |||
formSize: { | |||
type: String, | |||
default: "600px" | |||
}, | |||
closeOnClickModal: { | |||
type: Boolean, | |||
default: true | |||
} | |||
}); | |||
@@ -57,6 +57,10 @@ | |||
/> | |||
<el-button :disabled="!scope.isSelected" plain @click="omMove(FormOptEnum.ADD, scope.selectedListIds)" type="success">移动至分组</el-button> | |||
</template> | |||
<!-- 表格工具按钮 --> | |||
<template #toolButton> | |||
<el-button :icon="Refresh" circle @click="getTableList" /> | |||
</template> | |||
<!-- 表格 菜单类型 按钮 --> | |||
<!-- 操作 --> | |||
<template #operation="scope"> | |||
@@ -72,9 +76,9 @@ | |||
<el-dropdown-item :command="command(scope.row, cmdEnum.Delete)"> | |||
{{ cmdEnum.Delete }} | |||
</el-dropdown-item> | |||
<el-dropdown-item :command="command(scope.row, cmdEnum.pushPerson)"> | |||
<!-- <el-dropdown-item :command="command(scope.row, cmdEnum.pushPerson)"> | |||
{{ cmdEnum.pushPerson }} | |||
</el-dropdown-item> | |||
</el-dropdown-item> --> | |||
</el-dropdown-menu> | |||
</template> | |||
</el-dropdown> | |||
@@ -141,6 +145,7 @@ import { useDictStore } from "@/stores/modules"; | |||
import Form from "./components/form.vue"; | |||
import userForm from "./components/userForm.vue"; | |||
import moveForm from "./components/moveForm.vue"; | |||
import { Refresh } from "@element-plus/icons-vue"; | |||
// import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums"; | |||
// import './aliyun-rts-sdk.js' | |||
import "./ali.js"; | |||
@@ -369,6 +374,17 @@ const columns: ColumnProps<sysCamera.MonitorInfo>[] = [ | |||
return row.row.resWidth + "*" + row.row.resHeight; | |||
} | |||
}, | |||
{ | |||
prop: "deviceStatus", | |||
label: "在线状态", | |||
render: row => { | |||
if(row.row.deviceStatus){ | |||
return (<el-tag type="success">在线</el-tag>) | |||
}else{ | |||
return (<el-tag type="danger">离线</el-tag>) | |||
} | |||
} | |||
}, | |||
{ prop: "operation", label: "操作", width: 250, fixed: "right" } | |||
]; | |||
// 移动分组禁用 | |||
@@ -472,7 +488,7 @@ function getvideo1() { | |||
let aliRts = new AliRTS(); | |||
let pullStreamUrl = detailData.videoUrl; | |||
const mediaEle = document.querySelector("video"); | |||
aliRts.on("onError", err => { | |||
aliRts.on("onError", (err: any) => { | |||
console.log(`errorCode: ${err.errorCode}`); | |||
console.log(`message: ${err.message}`); | |||
}); | |||
@@ -574,7 +590,12 @@ function stopUrl() { | |||
}); | |||
}); | |||
} | |||
// 自定义刷新事件 | |||
function getTableList(){ | |||
monitorLIVEApi.setdat().then((res:any) => { | |||
RefreshTable(); | |||
}) | |||
} | |||
onMounted(() => { | |||
getGroupList(); | |||
}); | |||
@@ -0,0 +1,123 @@ | |||
<!-- | |||
* @Description: 表单 | |||
* @Author: syy | |||
* @Date: 2023-12-15 15:45:59 | |||
--> | |||
<template> | |||
<div> | |||
<form-container v-model="visible" :title="`${detailData.data.name}-关联摄像头`" :closeOnClickModal="false" form-size="800px" @close="onClose"> | |||
<el-transfer | |||
v-model="modelvalue" | |||
:data="tableData" | |||
filterable | |||
:titles="['待选择摄像头', '已选择摄像头']" | |||
:props="{ | |||
key: 'id', | |||
label: 'name' | |||
}" | |||
> | |||
<template #default="{ option }"> | |||
<span>{{ option.id }} - {{ option.name }}</span> | |||
</template> | |||
<template #left-empty> | |||
<el-empty :image-size="60" description="No data" /> | |||
</template> | |||
<template #right-empty> | |||
<el-empty :image-size="60" description="No data" /> | |||
</template> | |||
</el-transfer> | |||
<template #footer> | |||
<el-button @click="onClose"> 取消 </el-button> | |||
<el-button type="primary" @click="handleSubmit" :disabled="modelvalue.length == 0"> 确定 </el-button> | |||
</template> | |||
</form-container> | |||
</div> | |||
</template> | |||
<script setup lang="ts" name="SysUserPerformClass"> | |||
import { ref, reactive } from "vue"; | |||
import { abilityApi } from "@/api"; | |||
const visible = ref(false); //是否显示表单 | |||
const modelvalue = ref<any>([]); | |||
const tableData = ref<any>([]); | |||
let detailData: any = reactive({ | |||
parentCode: "", | |||
code: "", | |||
parentData: {}, | |||
data: {} | |||
}); | |||
// 表单参数 | |||
const sysUserProps = reactive<any>({ | |||
opt: "关联摄像头", | |||
record: {}, | |||
disabled: false | |||
}); | |||
/** | |||
* 打开表单 | |||
* @param props 表单参数 | |||
*/ | |||
function onOpen(props: any) { | |||
tableData.value = []; | |||
Object.assign(sysUserProps, props); //合并参数 | |||
visible.value = true; //显示表单 | |||
sysUserProps.record = props.record; | |||
detailData.parentData = props.record.parentData; | |||
detailData.data = props.record.data; | |||
modelvalue.value = detailData.data.cameraId; | |||
getCameraList(); | |||
//获取专业数据 | |||
} | |||
function getCameraList() { | |||
abilityApi.getFuncConf({ subsetCode: detailData.data.code }).then((res: any) => { | |||
tableData.value = res.data; | |||
}); | |||
} | |||
/** 提交表单 */ | |||
function handleSubmit() { | |||
let submitForm: any = []; | |||
// 过滤 | |||
tableData.value.map((item: any) => { | |||
modelvalue.value.map((key: any) => { | |||
if (item.id == key) { | |||
submitForm.push(item); | |||
} | |||
}); | |||
}); | |||
let cameraId: any = []; | |||
let cameraName: any = []; | |||
for (let i = 0; i < submitForm.length; i++) { | |||
cameraId.push(submitForm[i].id); | |||
cameraName.push(submitForm[i].name); | |||
} | |||
detailData.parentData.subset.map((item: any) => { | |||
if (item.code == detailData.data.code) { | |||
item.cameraId = cameraId; | |||
item.cameraName = cameraName; | |||
} | |||
}); | |||
console.log(detailData.parentData); | |||
let params: string = JSON.stringify([detailData.parentData]); | |||
abilityApi.setWarnGroup({ configJson: params }).then((res: any) => { | |||
let { code, data, msg } = res; | |||
if (code == 200 && data) { | |||
ElMessage.success(msg); | |||
onClose(); | |||
sysUserProps.successful!(); | |||
} | |||
}); | |||
} | |||
/** 关闭表单*/ | |||
function onClose() { | |||
visible.value = false; | |||
} | |||
// 暴露给父组件的方法 | |||
defineExpose({ | |||
onOpen | |||
}); | |||
</script> | |||
<style lang="scss" scoped> | |||
:deep(.el-transfer-panel) { | |||
width: 299px !important; | |||
} | |||
</style> |
@@ -84,12 +84,12 @@ async function handleSubmit() { | |||
warnCode: "", | |||
userId: "" | |||
}; | |||
if (liveUserProps.opt == "预警推送人") { | |||
params.warnCode = liveUserProps.record.warnCode; | |||
if (liveUserProps.opt == "视频推送人") { | |||
params.warnCode = liveUserProps.record.code; | |||
params.userId = liveUserProps.record.userId; | |||
//提交表单 | |||
await monitorLIVEApi | |||
.setWarningPushPerson(params) | |||
.setPushPersonByWarn(params) | |||
.then(() => { | |||
liveUserProps.successful!(); //调用父组件的successful方法 | |||
}) | |||
@@ -0,0 +1,202 @@ | |||
<!-- | |||
* @Description: 表单 | |||
* @Author: syy | |||
* @Date: 2023-12-15 15:45:59 | |||
--> | |||
<template> | |||
<div> | |||
<form-container v-model="visibleClass" :title="`${sysUserProps.opt}监控`" form-size="800px" @close="onClose"> | |||
<el-table :data="tableData" style="width: 100%"> | |||
<el-table-column label="摄像头名称" prop="cameraName" /> | |||
<el-table-column align="right"> | |||
<template #default="scope"> | |||
<el-button size="small" @click="handleView(scope.row)"> 查看 </el-button> | |||
</template> | |||
</el-table-column> | |||
</el-table> | |||
</form-container> | |||
<!-- 视频详情 --> | |||
<el-dialog v-model="visibleDialog" :title="detailData.title" width="830px" :before-close="handleClose"> | |||
<div> | |||
<div class="dialogHeader"> | |||
<div></div> | |||
<div class="dialogBtn" @click="refreshUrl"> | |||
<el-icon color="#409efc" :size="20"> | |||
<Refresh /> | |||
</el-icon> | |||
<div>刷新视频</div> | |||
</div> | |||
</div> | |||
<div v-if="visibleDialog || showVideo" class="prism-player" id="player-con"></div> | |||
</div> | |||
<template #footer> | |||
<div class="dialog-footer"> | |||
<el-button @click="handleClose">关闭</el-button> | |||
</div> | |||
</template> | |||
</el-dialog> | |||
</div> | |||
</template> | |||
<script setup lang="ts" name="SysUserPerformClass"> | |||
import { ref, reactive } from "vue"; | |||
import { monitorLIVEApi } from "@/api"; | |||
import { FormOptEnum } from "@/enums"; | |||
import "@/views/monitor/live/ali.js"; | |||
let detailData = reactive({ | |||
title: "", | |||
videoUrl: "", | |||
sensorId: "", | |||
streamId: "", | |||
videoToken: "", | |||
videoType: "" | |||
}); | |||
const visibleClass = ref(false); //是否显示表单 | |||
const visibleDialog = ref(false); //是否显示弹窗 | |||
// 表单参数 | |||
const sysUserProps = reactive<any>({ | |||
opt: FormOptEnum.ADD, | |||
record: {}, | |||
disabled: false | |||
}); | |||
/** | |||
* 打开表单 | |||
* @param props 表单参数 | |||
*/ | |||
let tableData = ref<any>([]); | |||
function onOpen(props: any) { | |||
tableData.value = []; | |||
Object.assign(sysUserProps, props); //合并参数 | |||
visibleClass.value = true; //显示表单 | |||
sysUserProps.record = props.record; | |||
for (let i = 0; i < props.record.cameraId.length; i++) { | |||
let obj: any = {}; | |||
obj["cameraId"] = props.record.cameraId[i]; | |||
obj["cameraName"] = props.record.cameraName[i]; | |||
tableData.value.push(obj); | |||
} | |||
console.log(tableData.value); | |||
//获取专业数据 | |||
} | |||
/** 关闭表单*/ | |||
function onClose() { | |||
visibleClass.value = false; | |||
} | |||
// 刷新 | |||
const showVideo = ref(false); | |||
function refreshUrl() { | |||
stopUrl(); | |||
showVideo.value = false; | |||
setTimeout(() => { | |||
showVideo.value = true; | |||
getUrl(); | |||
}, 1000); | |||
} | |||
function stopUrl() { | |||
detailData.videoUrl = ""; | |||
if (player.value) { | |||
player.value.dispose(); | |||
} | |||
let params = { | |||
sensorId: detailData.sensorId, | |||
streamId: detailData.streamId, | |||
videoToken: detailData.videoToken | |||
}; | |||
setTimeout(async () => { | |||
await monitorLIVEApi.stopUrl(params).then((res: any) => { | |||
let { code, data, msg } = res; | |||
if (code == 200) { | |||
// ElMessage.success(msg); | |||
} | |||
}); | |||
}); | |||
} | |||
function handleView(row: any) { | |||
detailData.sensorId = row.cameraId; | |||
detailData.title = row.cameraName + "(" + row.cameraId + ")"; | |||
visibleDialog.value = true; | |||
getUrl(); | |||
} | |||
function getUrl() { | |||
detailData.videoType = "m3u8"; | |||
setTimeout(async () => { | |||
await monitorLIVEApi.detail({ sensorId: detailData.sensorId }).then((res: any) => { | |||
let { code, data } = res; | |||
if (code == "200") { | |||
detailData.videoUrl = ""; | |||
detailData.videoUrl = data.rtsPullStreamUrls[0].url; | |||
detailData.streamId = data.streamId; | |||
detailData.videoToken = data.videoToken; | |||
getvideo2(); | |||
} | |||
}); | |||
}); | |||
} | |||
let player = ref<any>(null); | |||
function getvideo2() { | |||
var options = { | |||
id: "player-con", | |||
source: detailData.videoUrl + "&subaudio=no&jitterbuffer=6000", | |||
// "rtsFallbackSource": "降级地址,如HLS", | |||
width: "100%", | |||
height: "500px", | |||
autoplay: true, | |||
isLive: true, | |||
playsinline: true, | |||
skipRtsSupportCheck: false, // 对于不在 https://help.aliyun.com/document_detail/397569.html 中的浏览器,可以传 true 跳过检 | |||
rtsLoadDataTimeout: 1500, | |||
liveRetry: 1 | |||
}; | |||
player.value = new Aliplayer(options, function () {}); | |||
// 降级时会触发此事件 | |||
player.value.on("rtsFallback", function (event: any) { | |||
console.log("[EVENT]rtsFallback", event.paramData); | |||
}); | |||
player.value.on("error", function (event: any) { | |||
console.log("[EVENT]error", event.paramData); | |||
}); | |||
player.value.setVolume(0); | |||
player.value.on("rtsTraceId", function (data: any) { | |||
console.log("[EVENT]rtsTraceId", data.paramData); | |||
}); | |||
} | |||
const handleClose = () => { | |||
visibleDialog.value = false; | |||
detailData.videoUrl = ""; | |||
detailData.videoType = ""; | |||
stopUrl(); | |||
if (player.value) { | |||
player.value.dispose(); | |||
} | |||
}; | |||
// 暴露给父组件的方法 | |||
defineExpose({ | |||
onOpen | |||
}); | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "https://g.alicdn.com/apsara-media-box/imp-web-player/2.16.3/skins/default/aliplayer-min.css"; | |||
@import "@/views/monitor/live/index.scss"; | |||
.dialogHeader { | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
margin: 20px; | |||
font-size: 18px; | |||
color: #409efc; | |||
cursor: pointer; | |||
.dialogBtn { | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
} | |||
} | |||
</style> |
@@ -36,9 +36,11 @@ | |||
<div class="collapse-title" @click.stop=""> | |||
<div class="titlemodel"> | |||
<div style="width: 220px; text-align: left">{{ item.name }}</div> | |||
<div style="margin-left: 20px"> | |||
<div style="margin: 0 20px"> | |||
<el-button @click="pushPerson(FormOptEnum.VideoPushPerson, item)" type="primary" size="small">设置推送人</el-button> | |||
</div> | |||
<el-tag type="info" v-if="item.pushUserId">{{ item.pushUserName }}({{ item.pushPhone }})</el-tag> | |||
</div> | |||
<div class="btns"> | |||
<el-switch v-model="item.state" @change="stateChange" /> | |||
@@ -55,12 +57,22 @@ | |||
</div> --> | |||
<el-row :gutter="20"> | |||
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="6" v-for="v in item.subset" :key="v.code"> | |||
<div class="contentinfo"> | |||
<div class="contentinfo" style="position: relative"> | |||
<div class="modellabel">{{ v.name }}</div> | |||
<div class="btns"> | |||
<div class="btns" :style="{ justifyContent: v.cameraId.length > 0 ? 'space-between' : 'flex-end' }"> | |||
<el-button v-if="v.cameraId.length > 0" @click="viewMonitor(FormOptEnum.VIEW, v)" type="info" plain size="small" | |||
>查看监控</el-button | |||
> | |||
<el-switch :disabled="item.state == false" v-model="v.state" @change="stateChange" /> | |||
</div> | |||
<el-icon | |||
color="#409efc" | |||
:size="20" | |||
style="position: absolute; top: 10px; right: 10px" | |||
@click="addCamera(FormOptEnum.ADD, v, item)" | |||
> | |||
<Plus /> | |||
</el-icon> | |||
</div> | |||
</el-col> | |||
</el-row> | |||
@@ -70,18 +82,24 @@ | |||
</div> | |||
<!-- 人员选择 --> | |||
<userForm ref="userFormRef" /> | |||
<!-- 班级新增/编辑表单 --> | |||
<viewMonitorForm ref="formRefC" /> | |||
<!-- 关联摄像头 --> | |||
<cameraForm ref="cameraformRefC" /> | |||
</div> | |||
</div> | |||
</template> | |||
<script setup lang="tsx" name="ability"> | |||
import { ref, watch, provide, onMounted, unref, computed, reactive } from "vue"; | |||
import { ref, onMounted } from "vue"; | |||
import TreeFilter from "@/components/TreeFilter/index.vue"; | |||
import { ElMessage } from "element-plus"; | |||
import { abilityApi, userManageClassManageApi, monitorLIVEApi,SysOrg } from "@/api"; | |||
import { abilityApi, monitorLIVEApi,SysOrg } from "@/api"; | |||
// import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums"; | |||
import userForm from "./components/userForm.vue"; | |||
import viewMonitorForm from "./components/viewMonitor.vue"; | |||
import cameraForm from "./components/cameraForm.vue"; | |||
import { Plus} from "@element-plus/icons-vue"; | |||
enum FormOptEnum { | |||
/** 新增 */ | |||
ADD = "新增", | |||
@@ -150,6 +168,8 @@ function pushPerson(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) { | |||
} | |||
// 开关 | |||
function stateChange() { | |||
console.log(warnGroupList.value); | |||
let params: string = JSON.stringify(warnGroupList.value); | |||
setTimeout(async () => { | |||
await abilityApi.setWarnGroup({ configJson: params }).then((res:any) => { | |||
@@ -162,6 +182,20 @@ function stateChange() { | |||
}); | |||
}); | |||
} | |||
// 查看监控 | |||
const formRefC = ref<any>(null); | |||
function viewMonitor(opt: any, record: {}) { | |||
formRefC.value?.onOpen({ opt: opt, record: JSON.parse(JSON.stringify(record))}); | |||
} | |||
// 关联摄像头 | |||
const cameraformRefC = ref<any>(null); | |||
function addCamera(opt: any, record: {}, option: {}) { | |||
cameraformRefC.value?.onOpen({ opt: opt, record: { | |||
data:JSON.parse(JSON.stringify(record)), | |||
parentData:option | |||
},successful: getwarnGroup}); | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@@ -207,9 +241,12 @@ function stateChange() { | |||
font-size: 16px; | |||
} | |||
.btns { | |||
// text-align: right; | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
margin-top: 20px; | |||
margin-right: 0; | |||
text-align: right; | |||
} | |||
} | |||
} | |||
@@ -86,19 +86,17 @@ | |||
<div class="linebox">预警时间:{{ detailData.tick }}</div> | |||
</el-col> | |||
<el-col :span="12"> | |||
<!-- <el-col :span="12"> | |||
<div class="linebox"> | |||
复核视频: | |||
<span style="cursor: pointer" v-if="detailData.videoUrl" @click="onPlay(detailData.videoUrl)"> | |||
<el-icon color="#409efc" :size="20"> | |||
<VideoCamera /> | |||
</el-icon> | |||
<!-- <el-icon><VideoCamera /></el-icon> --> | |||
</span> | |||
<span v-else>暂无数据</span> | |||
<!-- {{ detailData.videoUrl }} --> | |||
</div> | |||
</el-col> | |||
</el-col> --> | |||
<el-col :span="12"> | |||
<div class="linebox">处理时间:{{ detailData.handTime ? detailData.handTime : "暂无数据" }}</div> | |||
</el-col> | |||
@@ -58,7 +58,7 @@ | |||
{{ detailData.tick || "--" }} | |||
</view> | |||
</view> | |||
<view class="cli"> | |||
<!-- <view class="cli"> | |||
<view class="labelBox"> | |||
复核视频: | |||
</view> | |||
@@ -67,7 +67,7 @@ | |||
@click="seeVideo(detailData.videoUrl)">查看视频</text> | |||
<text v-else>--</text> | |||
</view> | |||
</view> | |||
</view> --> | |||
<view class="cli"> | |||
<view class="labelBox"> | |||
处理时间: | |||