浏览代码

11

master
wwp 2 个月前
父节点
当前提交
b3bf79115e
共有 8 个文件被更改,包括 572 次插入464 次删除
  1. +1
    -0
      SafeCampus.WEB/src/api/interface/sys/index.ts
  2. +60
    -0
      SafeCampus.WEB/src/api/interface/sys/monitor/camera.ts
  3. +1
    -0
      SafeCampus.WEB/src/api/interface/sys/monitor/index.ts
  4. +34
    -9
      SafeCampus.WEB/src/api/modules/monitor/live.ts
  5. +15
    -17
      SafeCampus.WEB/src/views/monitor/live/components/moveForm.vue
  6. +21
    -16
      SafeCampus.WEB/src/views/monitor/live/index.scss
  7. +336
    -349
      SafeCampus.WEB/src/views/monitor/live/index.vue
  8. +104
    -73
      SafeCampus.WEB/src/views/sysconfig/ability/index.vue

+ 1
- 0
SafeCampus.WEB/src/api/interface/sys/index.ts 查看文件

@@ -21,3 +21,4 @@ export * from "./organization";
export * from "./auth";
export * from "./warn";
export * from "./usermanage";
export * from "./monitor";

+ 60
- 0
SafeCampus.WEB/src/api/interface/sys/monitor/camera.ts 查看文件

@@ -0,0 +1,60 @@
/**
* @description 单页管理接口
* @license Apache License Version 2.0
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
* @remarks
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关
*/
import { ReqPage } from "@/api/interface";
/**
* @Description: 单页管理接口
* @Author: wangwenpei
* @Date: 2023-12-15 15:34:54
*/
export namespace sysCamera {
/**单页分页查询 */
export interface Page extends ReqPage {
alarmType: "visual_fence";
}

/** 单页信息 */
export interface MonitorInfo {
/** id */
id: number | string;
deviceStatus: boolean;
directUrlIp: string;
fieldId: number | string;
fieldName: string;
groupId: number;
lastTime: string;
pushUserId: number;
resHeight: number;
resWidth: number;
sensorId: string;
sensorName: string;
/** 摄像头快照 */
snapshotUrl: string;
}
/**摄像头分页查询 */
export interface List extends ReqPage {
sensorName?: string;
sensorId?: number | string;
groupId?: number | string;
}
// 摄像头分组
export interface Tree extends MonitorInfo {
children: Tree[];
}
// 摄像头分组添加编辑删除信息
export interface MonitorGroupInfo {
/** id */
id: number | string;
name: string;
}
}

+ 1
- 0
SafeCampus.WEB/src/api/interface/sys/monitor/index.ts 查看文件

@@ -0,0 +1 @@
export * from "./camera";

+ 34
- 9
SafeCampus.WEB/src/api/modules/monitor/live.ts 查看文件

@@ -13,28 +13,53 @@
* @see https://gitee.com/dotnetmoyu/SimpleAdmin
*/
import { moduleRequest } from "@/api/request";
import { ReqstartId, ResPage, ZJRQ, ReqstopId } from "@/api/interface";
import { ReqstartId, ResPage, sysCamera, ReqstopId } from "@/api/interface";
const http = moduleRequest("/business/deviceApi/");
const http2 = moduleRequest("/business/cameraInfo/");
const http3 = moduleRequest("/business/cameraGroup/");

/**
* @Description: 单页管理
* @Author: huguodong
* @Author: wangwenpei
* @Date: 2023-12-15 15:34:54
*/
const monitorLIVEApi = {
/** 获取单页分页 */
page(params: ZJRQ.Page) {
return http.post<ResPage<ZJRQ.WarnInfo>>("brief", params);
page(params: sysCamera.Page) {
return http.post<ResPage<sysCamera.MonitorInfo>>("brief", params);
},
/** 获取单页详情 */
detail(params: ReqstartId) {
return http.get<ZJRQ.WarnInfo>("getStartVideoLive", params);
return http.get<sysCamera.MonitorInfo>("getStartVideoLive", params);
},
/** 停止视频流获取 */
stopUrl(params: ReqstopId) {
return http.get<sysCamera.MonitorInfo>("getStopVideoLive", params);
},
/** 获取监控列表 */
list(params: sysCamera.List) {
return http2.get<ResPage<sysCamera.MonitorInfo>>("getPageList", params);
},
// 批量设置分组
setGroup(params: sysCamera.setGroup) {
return http2.post<ResPage<sysCamera.MonitorInfo>>("batchSetGroup", params);
},
// 获取摄像头分组树
groupList(params: sysCamera.Tree) {
return http3.get<ResPage<sysCamera.MonitorInfo>>("getNoPageList");
},
// 添加摄像头分组树
addGroup(params: sysCamera.MonitorGroupInfo) {
return http3.post<ResPage<sysCamera.MonitorInfo>>("add", params);
},
/** 停止视频流获取 */
stopUrl(params: ReqstopId) {
return http.get<ZJRQ.WarnInfo>("getStopVideoLive", params);
// 编辑摄像头分组树
updateGroup(params: sysCamera.MonitorGroupInfo) {
return http3.put<ResPage<sysCamera.MonitorInfo>>("update", params);
},
// 删除摄像头分组树
deleteGroup(params: sysCamera.MonitorGroupInfo) {
return http3.delete<ResPage<sysCamera.MonitorInfo>>("delete", params);
}
};
/**
* @Description: 监控管理按钮权限码


+ 15
- 17
SafeCampus.WEB/src/views/monitor/live/components/moveForm.vue 查看文件

@@ -15,8 +15,8 @@
label-width="auto"
label-suffix=" :"
>
<s-form-item label="所属学校" prop="parentId">
<org-selector v-model:org-value="orgProps.record.parentId" :org-tree-api="bizOrgApi.tree" :show-all="false" />
<s-form-item label="摄像头分组" prop="id">
<org-selector v-model:org-value="orgProps.record.id" :org-tree-api="monitorLIVEApi.groupList" :show-all="false" />
</s-form-item>
</el-form>
<template #footer>
@@ -28,7 +28,7 @@
</template>

<script setup lang="ts">
import { SysOrg, SysUser, bizOrgApi, bizPositionApi, sysRoleApi, bizUserApi } from "@/api";
import { SysOrg, monitorLIVEApi, SysUser, bizOrgApi, bizPositionApi, sysRoleApi, bizUserApi } from "@/api";
import { FormOptEnum, SysDictEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
@@ -44,12 +44,13 @@ const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
const orgProps = reactive<FormProps.Base<SysOrg.SysOrgInfo>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
disabled: false,
records: []
});

// 表单验证规则
const rules = reactive({
parentId: [required("请选择所属学校")]
id: [required("请选择摄像头分组")]
});

/**
@@ -57,18 +58,12 @@ const rules = reactive({
* @param props 表单参数
*/
function omMove(props: FormProps.Base<SysOrg.SysOrgInfo>) {
// 合并参数
Object.assign(orgProps, props); //合并参数
if (props.opt == FormOptEnum.ADD) {
//如果是新增,设置默认值
orgProps.record.sortCode = 99;
// orgProps.record.status = statusOptions[0].value;
if (orgProps.opt == FormOptEnum.ADD) {
}
visible.value = true; //显示表单
if (props.record.id) {
//如果传了id,就去请求api获取record
bizOrgApi.detail({ id: props.record.id }).then(res => {
orgProps.record = res.data;
});
if (orgProps.record.id) {
}
}

@@ -79,10 +74,13 @@ async function handleSubmit() {
liveFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
console.log(orgProps);
return;
let params: any = {
id: orgProps.record.id,
ids: orgProps.records
};
//提交表单
await bizOrgApi
.submitForm(orgProps.record, orgProps.record.id != undefined)
await monitorLIVEApi
.setGroup(params)
.then(() => {
orgProps.successful!(); //调用父组件的successful方法
})


+ 21
- 16
SafeCampus.WEB/src/views/monitor/live/index.scss 查看文件

@@ -1,48 +1,53 @@
ul,li {
list-style: none;
padding: 0;
margin: 0;
}

.treeBox {
box-sizing: border-box;
width: 280px;
flex-shrink: 1;
width: 320px;
height: 100%;
padding: 14px;
margin-right: 10px;
flex-shrink: 1;
.title {
margin: 0 0 15px;
font-size: 18px;
font-weight: bold;
color: var(--el-color-info-dark-2);
letter-spacing: 0.5px;
}
.btn {

}
.treeContent {
padding: 10px 0;

// height: calc(100% - 100px);
// overflow: auto;
.el-tree-node__content {
height: 33px;
}
.custom-tree-node {
flex: 1;
display: flex;
flex: 1;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
font-size: 14px;
.node-label {
width: 100px;
overflow: hidden;
font-size: 16px;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 16px;
}
}
:deep(.el-tree--highlight-current) {
.el-tree-node.is-current > .el-tree-node__content {
color: white;
background-color: var(--el-color-primary);

// .el-tree-node__label,
// .el-tree-node__expand-icon {
// color: white;
// }
// .is-leaf {
// color: transparent;
// }
}
}
:deep(.el-tree-node__content) {
height: 50px;
@@ -74,5 +79,5 @@ ul,li {
}
}
.table-box {
width: calc(100% - 280px);
width: calc(100% - 320px);
}

+ 336
- 349
SafeCampus.WEB/src/views/monitor/live/index.vue 查看文件

@@ -1,44 +1,39 @@
<!--
* @Description: 告警管理
* @Author: huguodong
* @Date: 2023-12-15 15:44:05
* @Description: 监控管理
* @Author: wangwenpei
* @Date: 2024-07-18 15:44:05
!-->
<template>
<div class="main-box">
<div class="card treeBox">
<p class="title">摄像头分组管理</p>
<div class="btn">
<el-button @click="append('add', {})" type="primary">添加分组</el-button>
<el-button icon="CirclePlus" @click="append('add', {})" type="primary">添加分组</el-button>
</div>
<!-- <div class="treeContent">
<ul class="treeList" v-for="(item, i) in treeData" :key="i">
<li class="treeItem">
<div class="treeLabel">{{ item.label }}</div>
<div class="treeBtn">
<el-icon><Edit /></el-icon>
<el-icon><Delete /></el-icon>
</div>
</li>
</ul>
</div> -->
<div class="treeContent">
<el-input v-model="filterText" style="width: 240px" placeholder="请输入关键字" />
<el-tree
style="max-width: 600px"
ref="treeRef"
:data="treeData"
node-key="id"
default-expand-all
:props="defaultProps"
:expand-on-click-node="false"
:check-on-click-node="true"
:highlight-current="true"
:filter-node-method="filterNode"
:current-node-key="defaultHighlightKey"
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span class="node-label" :title="node.label">{{ node.label }}</span>
<span v-if="data.id && data.id != '-1'">
<el-icon size="16" @click.stop="append('edit', data)"><Edit /></el-icon>
<el-icon size="16" @click.stop="pushPerson('push', data)" style="margin-left: 8px"><UserFilled /></el-icon>
<el-icon size="16" @click.stop="remove(node, data)" style="margin-left: 8px"><Delete /></el-icon>
<el-icon size="16" title="添加" @click.stop="append('add', data)"><CirclePlus /></el-icon>
<el-icon size="16" title="编辑" @click.stop="append('edit', data)" style="margin-left: 8px"><Edit /></el-icon>
<el-icon size="16" title="设置推送人" @click.stop="pushPerson('push', data)" style="margin-left: 8px"><UserFilled /></el-icon>
<el-icon size="16" title="删除" @click.stop="remove(data.id, '删除该分组')" style="margin-left: 8px"><Delete /></el-icon>
</span>
</span>
</template>
@@ -46,7 +41,7 @@
</div>
</div>
<div class="table-box">
<ProTable ref="proTable" title="视频列表" :columns="columns" :request-api="monitorLIVEApi.page">
<ProTable ref="proTable" title="视频列表" :columns="columns" :request-api="monitorLIVEApi.list">
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<s-button v-auth="monitorLiveButtonCode.add" suffix="摄像头" @click="onOpen(FormOptEnum.ADD)" />
@@ -58,21 +53,9 @@
:disabled="!scope.isSelected"
@click="onDelete(scope.selectedListIds, '删除所选数据')"
/>
<el-button plain @click="omMove(FormOptEnum.ADD)" type="success">移动至分组</el-button>
<el-button :disabled="!scope.isSelected" plain @click="omMove(FormOptEnum.ADD, scope.selectedListIds)" type="success">移动至分组</el-button>
</template>
<!-- 表格 菜单类型 按钮 -->
<!-- <template #menuType="scope">
<el-space wrap>
<el-tag v-if="scope.row.menuType === MenuTypeDictEnum.MENU" type="success">{{
dictStore.dictTranslation(SysDictEnum.MENU_TYPE, MenuTypeDictEnum.MENU)
}}</el-tag>
<el-tag v-else-if="scope.row.menuType === MenuTypeDictEnum.LINK" type="warning">{{
dictStore.dictTranslation(SysDictEnum.MENU_TYPE, MenuTypeDictEnum.LINK)
}}</el-tag>
<el-tag v-else type="info">{{ dictStore.dictTranslation(SysDictEnum.MENU_TYPE, scope.row.menuType) }}</el-tag>
<el-tag v-if="scope.row.isHome === true" type="danger">首页</el-tag>
</el-space>
</template> -->
<!-- 操作 -->
<template #operation="scope">
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)">编辑</s-button>
@@ -103,7 +86,7 @@
<!-- 移动至分组 -->
<moveForm ref="moveFormRef" />
<!-- 视频详情 -->
<el-dialog v-model="visible" :title="detailData.title" width="830px" :before-close="closeGroup">
<el-dialog v-model="visible" :title="detailData.title" width="830px" :before-close="handleClose">
<div>
<div class="dialogHeader">
<div></div>
@@ -128,187 +111,195 @@
</div>
</div>
</template>
<script setup lang="tsx" name="sysSpa">
import VideoPlay from "@/components/VideoPlay/videoplay.vue";
import { ElMessage,ElMessageBox } from "element-plus";
import type Node from 'element-plus/es/components/tree/src/model/node'
import { monitorLIVEApi, monitorLiveButtonCode } from "@/api";
import { ZJRQ } from "@/api/interface";
import { useHandleData } from "@/hooks/useHandleData";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { useDictStore } from "@/stores/modules";
import Form from "./components/form.vue";
import userForm from "./components/userForm.vue";
import moveForm from "./components/moveForm.vue";
import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums";
// import './aliyun-rts-sdk.js'
import './ali.js'
const groupVisible = ref(false); //是否显示表单
const groupTitle = ref('新增分组');
const groupForm = reactive({
name: ''
});
const groupRules = ref({
name: [{ required: true, message: '请输入分组名称', trigger: 'blur' }]
<script setup lang="tsx" name="sysSpa">
import VideoPlay from "@/components/VideoPlay/videoplay.vue";
import { ElMessage, ElMessageBox, ElTree } from "element-plus";
import type Node from "element-plus/es/components/tree/src/model/node";
import { monitorLIVEApi, monitorLiveButtonCode } from "@/api";
import { ZJRQ } from "@/api/interface";
import { useHandleData } from "@/hooks/useHandleData";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { useDictStore } from "@/stores/modules";
import Form from "./components/form.vue";
import userForm from "./components/userForm.vue";
import moveForm from "./components/moveForm.vue";
import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums";
// import './aliyun-rts-sdk.js'
import "./ali.js";
// 分组字段配置
const defaultProps = {
children: "children",
label: "name"
};
// 默认高亮
const defaultHighlightKey = ref("");

let treeDada1 = ref([
{
id: "",
name: "全部"
}
]);
const treeData = ref<any>([]);
var treeRef = ref<InstanceType<typeof ElTree>>();
// 获取摄像头分组
function getGroupList() {
monitorLIVEApi.groupList({}).then((res:any) => {
if (res.code == 200) {
treeData.value = [...treeDada1.value, ...res.data];
}
});
const groupFormRef = ref(null);
const closeGroup = () => {
groupVisible.value = false;
groupFormRef.value.resetFields();
groupForm.name = ''
}

const groupVisible = ref(false); //是否显示表单
const groupTitle = ref("新增分组");
const groupForm = reactive({
name: "",
id: ""
});
const groupRules = ref({
name: [{ required: true, message: "请输入分组名称", trigger: "blur" }]
});
const groupFormRef = ref<any>(null);
const closeGroup = () => {
groupVisible.value = false;
groupFormRef.value.resetFields();
groupForm.name = "";
};
// 新增编辑分组
const append = (type: string, data: Tree) => {
groupVisible.value = true;
if (type == "edit") {
groupForm.name = data.name;
groupForm.id = data.id;
}
const onSubmit = () => {
groupFormRef.value.validate((valid:any) => {
if (valid) {
closeGroup()
ElMessage.success('提交成功');
};
// 删除分组
async function remove(id: string[], msg: string) {
await useHandleData(monitorLIVEApi.deleteGroup, { id }, msg);
getGroupList();
}
// 提交分组
const onSubmit = () => {
groupFormRef.value.validate((valid: any) => {
if (valid) {
let params: any = reactive({
id: "",
name: groupForm.name
});
if (!groupForm.id) {
monitorLIVEApi.addGroup(params).then(res => {
if (res.code == 200) {
getGroupList();
closeGroup();
}
});
} else {
return false;
params.id = groupForm.id;
monitorLIVEApi.updateGroup(params).then(res => {
if (res.code == 200) {
getGroupList();
closeGroup();
}
});
}
});
};
// 摄像头分组
closeGroup();
} else {
return false;
}
});
};
// 摄像头分组过滤
interface Tree {
id: any
label: string
children?: Tree[]
[key: string]: any;
id: any;
label: string;
children?: Tree[];
}
let id = 1000
const filterText = ref("");

watch(filterText, val => {
treeRef.value!.filter(val);
});

const filterNode = (value: string, data: Tree) => {
if (!value) return true;
return data.name.includes(value);
};

const handleNodeClick = (data: Tree) => {
console.log(data)
}
console.log(data);
// personSetId.value = val
proTable.value!.pageable.pageNum = 1;
proTable.value!.searchParam.groupId = data.id;
proTable.value!.search();
};

// 设置分组推送人
// async function pushPerson(type:string,data: Tree) {

// }
const visible = ref(false); //是否显示表单

// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
const dictStore = useDictStore();

const treeData = ref<Tree[]>([
// 表格配置项
const columns: ColumnProps<ZJRQ.WarnInfo>[] = [
{ type: "selection", fixed: "left", width: 80 },
{
id: '',
label: '全部',
prop: "sensorId",
label: "摄像头ID"
},
{
id: '-1',
label: '无分组',
prop: "sensorName",
label: "摄像头名称"
},
{
id: 1,
label: '走廊',
prop: "fieldName",
label: "所属学校"
},
{
id: 2,
label: '大厅',
prop: "directUrlIp",
label: "设备ip"
},
{
id: 3,
label: '厨房',
prop: "resWidth",
label: "分辨率",
render: row => {
return row.row.resWidth + "*" + row.row.resHeight;
}
},
])
// 新增分组
const append = (type:string,data: Tree) => {
console.log(type,data)
groupVisible.value = true;
if(type == 'edit') {
groupForm.name = treeData.value.find(item => item.id ==data.id).label;
}
{ prop: "operation", label: "操作", width: 250, fixed: "right" }
];
// 移动分组禁用
const moveFormRef = ref<InstanceType<typeof Form> | null>(null);
function omMove(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
console.log(record,"record")

// const newChild = { id: id++, label: 'testtest', children: [] }
// if (!data.children) {
// data.children = []
// }
// data.children.push(newChild)
// treeData.value = [...treeData.value]
moveFormRef.value?.omMove({ opt: opt, records: record, successful: RefreshTable });
}
// 删除分组
const remove = (node: Node, data: Tree) => {
const parent = node.parent
const children: Tree[] = parent.data.children || parent.data
const index = children.findIndex((d) => d.id === data.id)
children.splice(index, 1)
treeData.value = [...treeData.value]
/**
* 删除
* @param ids id数组
*/
async function onDelete(ids: string[], msg: string) {
return;
// 二次确认 => 请求api => 刷新表格
await useHandleData(monitorLIVEApi.delete, { ids }, msg);
RefreshTable();
}
// 设置分组推送人
// async function pushPerson(type:string,data: Tree) {
// }
const visible = ref(false); //是否显示表单
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();
const dictStore = useDictStore();
// 表格配置项
const columns: ColumnProps<ZJRQ.WarnInfo>[] = [
{ type: "selection", fixed: "left", width: 80 },
// { prop: "searchKey", label: "关键字", search: { el: "input" }, isShow: false },
// {
// prop: "poiId",
// label: "所属学校",
// render: () => {
// return "演示学校";
// }
// },
{
prop: "sensorId",
label: "摄像头ID",
// render: () => {
// return "楼道";
// }
},
{
prop: "sensorName",
label: "摄像头名称",
// render: () => {
// return "楼道";
// }
},
{
prop: "fieldName",
label: "所属学校",
},
{
prop: "directUrlIp",
label: "设备ip",
},
{
prop: "resWidth",
label: "分辨率",
render: (row) => {
return row.row.resWidth + '*' + row.row.resHeight;
}
},
{ prop: "operation", label: "操作", width: 250, fixed: "right" }
];

/**
* 删除
* @param ids id数组
*/
async function onDelete(ids: string[], msg: string) {
return
// 二次确认 => 请求api => 刷新表格
await useHandleData(monitorLIVEApi.delete, { ids }, msg);
RefreshTable();
}



const moveFormRef = ref<InstanceType<typeof Form> | null>(null);
function omMove(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
moveFormRef.value?.omMove({ opt: opt, record: record, successful: RefreshTable });
/**
* 刷新表格
*/
function RefreshTable() {
proTable.value?.refresh();
}
/**
* 刷新表格
*/
function RefreshTable() {
proTable.value?.refresh();
}
// 表单引用
// 表单引用
const formRef = ref<InstanceType<typeof Form> | null>(null);
/**
/**
* 打开表单
* @param opt 操作类型
* @param record 记录
@@ -318,9 +309,9 @@ function onOpen(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
}

// 人员选择
// 表单引用
const userFormRef = ref<InstanceType<typeof Form> | null>(null);
/**
// 表单引用
const userFormRef = ref<InstanceType<typeof Form> | null>(null);
/**
* 打开表单
* @param opt 操作类型
* @param record 记录
@@ -328,183 +319,179 @@ function onOpen(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
function pushPerson(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
userFormRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
}
// 详情数据
// let detailData: globalThis.Ref<{}>
let detailData = reactive({
title: '',
videoUrl: '',
sensorId: '',
streamId: '',
videoToken:'',
videoType: ''
});
function onDetail(row: any) {
visible.value = true;
detailData.sensorId = row.sensorId;
detailData.title = row.sensorName + '('+ row.sensorId + ')';
getUrl()
}
// 刷新
// var showVideo = false
const showVideo = ref(false)
function refreshUrl() {
stopUrl();
showVideo.value = false;
setTimeout(()=> {
showVideo.value = true;
getUrl()
},1000)
}
let num = 1;
function getUrl() {
detailData.videoType = 'm3u8'
setTimeout(async ()=> {
await monitorLIVEApi.detail({ sensorId: detailData.sensorId}).then(res => {
let { code, data } = res;
if (code == 200) {
// window.location.origin+
detailData.videoUrl = '';
detailData.videoUrl= data.rtsPullStreamUrls[0].url;
// detailData.videoUrl = data.pullStreamUrls[2].url;
detailData.streamId = data.streamId;
detailData.videoToken = data.videoToken;
// getvideo1()
getvideo2()
}
});
})
}
function getvideo1() {
let aliRts = new AliRTS()
// let url = detailData.videoUrl.replace('http://rts-pull-live.deepeleph.com', '/Files')
// console.log(url,888)
let pullStreamUrl = detailData.videoUrl;
const mediaEle = document.querySelector('video');
aliRts.on("onError", (err) => {
console.log(`errorCode: ${err.errorCode}`);
console.log(`message: ${err.message}`);
})
// 详情数据
// let detailData: globalThis.Ref<{}>
let detailData = reactive({
title: "",
videoUrl: "",
sensorId: "",
streamId: "",
videoToken: "",
videoType: ""
});
function onDetail(row: any) {
visible.value = true;
detailData.sensorId = row.sensorId;
detailData.title = row.sensorName + "(" + row.sensorId + ")";
getUrl();
}
// 刷新
const showVideo = ref(false);
function refreshUrl() {
stopUrl();
showVideo.value = false;
setTimeout(() => {
showVideo.value = true;
getUrl();
}, 1000);
}
let num = 1;
function getUrl() {
detailData.videoType = "m3u8";
setTimeout(async () => {
await monitorLIVEApi.detail({ sensorId: detailData.sensorId }).then((res:any) => {
let { code, data } = res;

const PLAY_EVENT = {
CANPLAY: "canplay",
WAITING: "waiting",
PLAYING: "playing"
}
if (code == '200') {
// window.location.origin+
detailData.videoUrl = "";
detailData.videoUrl = data.rtsPullStreamUrls[0].url;
// detailData.videoUrl = data.pullStreamUrls[2].url;
detailData.streamId = data.streamId;
detailData.videoToken = data.videoToken;
// getvideo1()
getvideo2();
}
});
});
}
function getvideo1() {
let aliRts = new AliRTS();
let pullStreamUrl = detailData.videoUrl;
const mediaEle = document.querySelector("video");
aliRts.on("onError", err => {
console.log(`errorCode: ${err.errorCode}`);
console.log(`message: ${err.message}`);
});

aliRts.on('onPlayEvent', (play) => {
console.log(">>play.event:" + play.event);
if (play.event === PLAY_EVENT.CANPLAY) {
// 拉流可以播放
console.log('可以播放')
} else if (play.event === PLAY_EVENT.WAITING) {
// 拉流卡顿等待缓冲中 (仅Chrome)
console.log('拉流卡顿等待缓冲中')
const PLAY_EVENT = {
CANPLAY: "canplay",
WAITING: "waiting",
PLAYING: "playing"
};

} else if (play.event === PLAY_EVENT.PLAYING) {
// 拉流卡顿结束恢复播放 (仅Chrome)
console.log('拉流卡顿结束恢复播放')
}
});
aliRts.startLiveStream(pullStreamUrl, mediaEle);
// aliRts.startLiveStream(pullStreamUrl, mediaEle);
}
let player = ref(null);
function getvideo2() {
var options = {
"id": "player-con",
"source": detailData.videoUrl + '&subaudio=no&jitterbuffer=6000',
// "rtsFallbackSource": "降级地址,如HLS",
"width": "100%",
"height": "600px",
"autoplay": true,
"isLive": true,
"playsinline": true,
"skipRtsSupportCheck": false, // 对于不在 https://help.aliyun.com/document_detail/397569.html 中的浏览器,可以传 true 跳过检查,强制使用 RTS(有风险,需要自测保证)
aliRts.on("onPlayEvent", play => {
console.log(">>play.event:" + play.event);
if (play.event === PLAY_EVENT.CANPLAY) {
// 拉流可以播放
console.log("可以播放");
} else if (play.event === PLAY_EVENT.WAITING) {
// 拉流卡顿等待缓冲中 (仅Chrome)
console.log("拉流卡顿等待缓冲中");
} else if (play.event === PLAY_EVENT.PLAYING) {
// 拉流卡顿结束恢复播放 (仅Chrome)
console.log("拉流卡顿结束恢复播放");
}
});
aliRts.startLiveStream(pullStreamUrl, mediaEle);
// aliRts.startLiveStream(pullStreamUrl, mediaEle);
}
let player = ref<any>(null);
function getvideo2() {
var options = {
id: "player-con",
source: detailData.videoUrl + "&subaudio=no&jitterbuffer=6000",
// "rtsFallbackSource": "降级地址,如HLS",
width: "100%",
height: "600px",
autoplay: true,
isLive: true,
playsinline: true,
skipRtsSupportCheck: false, // 对于不在 https://help.aliyun.com/document_detail/397569.html 中的浏览器,可以传 true 跳过检查,强制使用 RTS(有风险,需要自测保证)

/**
* RTS 拉流超时会默认重试
* 以下两个参数用来控制降级之前的重试策略,比如 3000 毫秒超时,重试一次,如果再拉不到流就降级,那么总共等待 6000 毫秒降级
**/
// RTS 多久拉不到流会重试,默认 3000 ms
rtsLoadDataTimeout: 1500,
/**
* RTS 拉流超时会默认重试
* 以下两个参数用来控制降级之前的重试策略,比如 3000 毫秒超时,重试一次,如果再拉不到流就降级,那么总共等待 6000 毫秒降级
**/
// RTS 多久拉不到流会重试,默认 3000 ms
rtsLoadDataTimeout: 1500,

// RTS 拉不到流重试的次数,默认 5,此参数建议设为 1,即重试 1 次后降级,可以减少降级等待时间
liveRetry: 1,
};
// RTS 拉不到流重试的次数,默认 5,此参数建议设为 1,即重试 1 次后降级,可以减少降级等待时间
liveRetry: 1
};

player.value = new Aliplayer(options, function () {/* player ready */ })
player.value = new Aliplayer(options, function () {
/* player ready */
});

// 降级时会触发此事件
player.value.on('rtsFallback', function (event) {
console.log('[EVENT]rtsFallback', event.paramData);
// event.paramData.reason 降级的原因
// event.paramData.fallbackUrl 降级到的地址
})
// 降级时会触发此事件
player.value.on("rtsFallback", function (event:any) {
console.log("[EVENT]rtsFallback", event.paramData);
// event.paramData.reason 降级的原因
// event.paramData.fallbackUrl 降级到的地址
});

player.value.on('error', function (event) {
console.log('[EVENT]error', event.paramData);
})
player.value.on("error", function (event:any) {
console.log("[EVENT]error", event.paramData);
});

// 当RTS拉流成功时触发,通过订阅该事件,可以获取到RTS TraceId
player.value.on('rtsTraceId', function (data) {
console.log('[EVENT]rtsTraceId', data.paramData);
// event.paramData.traceId 拉流的TraceId
// event.paramData.source 当前RTS流的播放地址
})
// 当RTS拉流成功时触发,通过订阅该事件,可以获取到RTS TraceId
player.value.on("rtsTraceId", function (data:any) {
console.log("[EVENT]rtsTraceId", data.paramData);
// event.paramData.traceId 拉流的TraceId
// event.paramData.source 当前RTS流的播放地址
});
}
const handleClose = () => {
visible.value = false;
detailData.videoUrl = "";
detailData.videoType = "";
stopUrl();
if (player.value) {
player.value.dispose();
}
const handleClose = () => {
visible.value = false;
detailData.videoUrl = ''
detailData.videoType = ''
stopUrl();
if(player.value) {
player.value.dispose();
}
};
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 => {
let { code, data, msg } = res;
if (code == 200) {
// ElMessage.success(msg);
}
});
})
};
function stopUrl() {
detailData.videoUrl = "";
if (player.value) {
player.value.dispose();
}
</script>
<style lang="scss" scoped>
@import url("https://g.alicdn.com/apsara-media-box/imp-web-player/2.16.3/skins/default/aliplayer-min.css");
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);
}
});
});
}

onMounted(() => {
getGroupList();
});
</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 "./index.scss";
.dialogHeader {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: space-between;
margin: 20px;
color: #409efc;
font-size: 18px;
color: #409efc;
cursor: pointer;
.dialogBtn {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: space-between;
}
}
</style>

+ 104
- 73
SafeCampus.WEB/src/views/sysconfig/ability/index.vue 查看文件

@@ -4,52 +4,89 @@
* @Date: 2023-12-15 15:44:05
!-->
<template>
<div class="card content-main">
<el-collapse accordion>
<el-collapse-item :name="ins" v-for="(item, ins) in warnGroupList" :key="item.cameraId[0]">
<template #title>
<div class="collapse-title" @click.stop="">
<div class="titlemodel">{{ item.name }} {{ item.cameraId[0] }}</div>
<div class="btns">
<el-switch v-model="item.state" @change="stateChange" />
<div class="abilityBox">
<TreeFilter ref="treeFilter" label="name" id="id" width="300px" :request-api="monitorLIVEApi.groupList" @change="changeGroup"> </TreeFilter>

<TreeFilter ref="treeFilters" label="sensorName" id="sensorId" :isData="true" width="300px" :data="videoList" @change="changeVideo"> </TreeFilter>

<div class="card content-main">
<el-collapse accordion>
<el-collapse-item :name="ins" v-for="(item, ins) in warnGroupList" :key="item.cameraId[0]">
<template #title>
<div class="collapse-title" @click.stop="">
<div class="titlemodel">{{ item.name }} {{ item.cameraId[0] }}</div>
<div class="btns">
<el-switch v-model="item.state" @change="stateChange" />
</div>
</div>
</div>
</template>
<div class="collapse-content">
<!-- <div class="contentinfo" v-for="(v, index) in item.subset" :key="v.code">
</template>
<div class="collapse-content">
<!-- <div class="contentinfo" v-for="(v, index) in item.subset" :key="v.code">
<div class="modellabel">{{ v.name }}</div>

<div class="btns">
<el-switch v-model="v.state" active-text="开启" inactive-text="关闭" />
</div>
</div> -->
<el-row :gutter="20">
<el-col :span="6" v-for="(v, index) in item.subset" :key="v.code">
<div class="contentinfo">
<div class="modellabel">{{ v.name }}</div>
<el-row :gutter="20">
<el-col :span="6" v-for="(v, index) in item.subset" :key="v.code">
<div class="contentinfo">
<div class="modellabel">{{ v.name }}</div>

<div class="btns">
<el-switch v-model="v.state" @change="stateChange" />
<div class="btns">
<el-switch v-model="v.state" @change="stateChange" />
</div>
</div>
</div>
</el-col>
</el-row>
</div>
</el-collapse-item>
</el-collapse>
</el-col>
</el-row>
</div>
</el-collapse-item>
</el-collapse>
</div>
</div>
</template>
<script setup lang="tsx" name="sysSpa">
import { ref, watch, provide, onMounted, unref, computed, reactive } from "vue";
import TreeFilter from "@/components/TreeFilter/index.vue";
import { ElMessage } from "element-plus";
import { abilityApi } from "@/api";
import { abilityApi, userManageClassManageApi, monitorLIVEApi } from "@/api";

const value = ref(true);
onMounted(() => {
// 在这里执行其他需要在组件挂载后运行的代码
getwarnGroup();
getVideoList()
});
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null);
const treeFilters = ref<InstanceType<typeof TreeFilter> | null>(null);
function changeGroup(val: number | string) {
console.log(val)
// personSetId.value = val
// proTable.value!.pageable.pageNum = 1;
// proTable.value!.searchParam.personSetId = val;
// proTable.value!.search();
// getVideoList(val.id)
}
const videoList = ref<any>();
function getVideoList() {
setTimeout(async () => {
await monitorLIVEApi.list({ pageNum: 1, pageSize: 1000 }).then(res => {
videoList.value = res.data.list;
});
});
}
function changeVideo(val: number | string) {
console.log(val)
// personSetId.value = val
// proTable.value!.pageable.pageNum = 1;
// proTable.value!.searchParam.personSetId = val;
// proTable.value!.search();
// getVideoList(val.id)
}
// 获取配置树

// 获取配置树
let warnGroupList = ref([]);
function getwarnGroup() {
setTimeout(async () => {
@@ -62,7 +99,7 @@ function getwarnGroup() {
});
});
}
// 开关
function stateChange() {
let params: string = JSON.stringify(warnGroupList.value);
setTimeout(async () => {
@@ -77,60 +114,54 @@ function stateChange() {
});
}
</script>
<style lang="scss" scoped>
.content-main {
::v-deep(.el-collapse-item__header) {
flex: 1 0 auto;
order: -1;
height: 50px;
line-height: 50px;
}

.collapse-title {
flex: 1 0 90%;
order: 1;
display: flex;
align-items: center;
justify-content: space-between;
// padding: 30px 0;

.titlemodel {
// display: inline-block;
font-size: 18px;
margin-right: 40px;
}
<style lang="scss" scoped>
.abilityBox {
display: flex;
width: 100%;
height: 100%;

.btns {
margin-left: 40px;
// 配置列表
.content-main {
width: calc(100% - 630px);
::v-deep(.el-collapse-item__header) {
flex: 1 0 auto;
order: -1;
height: 50px;
line-height: 50px;
}
}
.collapse-content {
padding: 20px;
.contentinfo {
margin-top: 20px;
// display: flex;
// justify-content: space-between;
// align-items: center;
padding: 20px 20px;
box-sizing: border-box;
// border-bottom: 1px solid #dcdfe6;
// background: red;
border-radius: 10px;
// box-shadow: 3px 3px 3px #00000014, 3px -3px 3px #00000014, -3px 3px 3px #00000014, -3px -3px 3px #00000014;
// box-shadow: 0 1px 1px hsl(0deg 0% 0% / 0.075), 0 2px 2px hsl(0deg 0% 0% / 0.075), 0 4px 4px hsl(0deg 0% 0% / 0.075),
// 0 8px 8px hsl(0deg 0% 0% / 0.075), 0 16px 16px hsl(0deg 0% 0% / 0.075);
box-shadow: 0px 2px 1px rgba(0, 0, 0, 0.1), 0 0px 8px rgba(0, 0, 0, 0.1), 0 -1px 1px #fff, 0px 0 0px #fff, 0 0 16px #fff;
.modellabel {
font-size: 16px;
.collapse-title {
display: flex;
flex: 1 0 90%;
align-items: center;
justify-content: space-between;
order: 1;
.titlemodel {
margin-right: 40px;
font-size: 18px;
}
.btns {
margin-left: 40px;
}
}
.collapse-content {
padding: 20px;
.contentinfo {
box-sizing: border-box;
padding: 20px;
margin-top: 20px;
margin-right: 0;
text-align: right;
border-radius: 10px;
box-shadow: 0 2px 1px rgb(0 0 0 / 10%), 0 0 8px rgb(0 0 0 / 10%), 0 -1px 1px #ffffff, 0 0 0 #ffffff, 0 0 16px #ffffff;
.modellabel {
font-size: 16px;
}
.btns {
margin-top: 20px;
margin-right: 0;
text-align: right;
}
}
}
}
}
</style>

正在加载...
取消
保存