const apiEndpoint = '/api/channel/list'; const rowsPerPage = 10; //算法配置窗口部分控件 const searchEndpoint = '/api/channel/select'; const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); const backgroundCanvas = document.getElementById('backgroundCanvas'); const backgroundCtx = backgroundCanvas.getContext('2d'); const img = new Image(); const tbody = document.getElementById('schedule-body');//布防计划 let currentPage = 1; let channelData = []; let channelData_bak = []; let areaData = ["请选择"]; let currentEditingRow = null; let cid_schedule = "-1"; let m_polygon = ""; let check_area = 0; let draw_status = false; //是否是绘制状态,处于绘制状态才能开始绘制 let b_img = false; //有没有加载图片成功,如果没有初始化的时候就不绘制线条了。 let points = []; //布防计划 document.addEventListener('DOMContentLoaded', function () { fetchChannelData(); //初始化通道管理页面元素数据 document.getElementById('searchButton').addEventListener('click', function () { performSearch(); }); //新增通道模块--保存按钮 document.getElementById('saveButton').addEventListener('click', function () { addChannel(1); }); //修改通道模块--保存按钮 document.getElementById('saveButton_cc').addEventListener('click', function () { addChannel(2); }); //算法配置模块--取消按钮 document.getElementById('cancelButton_mx').addEventListener('click', function () { close_mx_model(); }); //保存算法配置--保存按钮 document.getElementById('saveButton_mx').addEventListener('click', function () { save_mx_model(); }); //开始绘制区域按钮 document.getElementById('but_hzqy').addEventListener('click', function () { startDraw(); }); }); //添加和修改通道 1--新增,2--修改 function addChannel(itype) { let area; let cName; let Rtsp; let cid; const spinnerOverlay = document.getElementById("spinnerOverlay"); let saveButton = null; let CNameInput = null; let RTSPInput = null; if(itype ==1){ saveButton = document.getElementById('saveButton'); CNameInput = document.getElementById('CNameInput'); RTSPInput = document.getElementById('RTSPInput'); area = document.getElementById('areaSelect_M').value; cid = -1 } else if(itype ==2){ saveButton = document.getElementById('saveButton_cc'); CNameInput = document.getElementById('CNameInput_cc'); RTSPInput = document.getElementById('RTSPInput_cc'); area = document.getElementById('areaSelect_CC').value; cid = currentEditingRow.cells[0].innerText; } console.log("点击了保存按钮"); cName = CNameInput.value.trim(); Rtsp = RTSPInput.value.trim(); if(area === "请选择"){ alert('请选择所属区域'); } else{ if (cName && Rtsp) { saveButton.disabled = true; //发送视频链接接口 const url = '/api/channel/add'; const data = {"area":area,"cName":cName,"Rtsp":Rtsp,"cid":cid}; // 显示 Spinners spinnerOverlay.style.display = "flex"; // 发送 POST 请求 fetch(url, { method: 'POST', // 指定请求方法为 POST headers: { 'Content-Type': 'application/json' // 设置请求头,告诉服务器请求体的数据类型为 JSON }, body: JSON.stringify(data) // 将 JavaScript 对象转换为 JSON 字符串 }) .then(response => response.json()) // 将响应解析为 JSON .then(data => { const istatus = data.status; saveButton.disabled = false; alert(data.msg); // 使用 Modal 显示消息 if(istatus == 1){ //刷新列表 fetchChannelData(); if(itype ==1){ //添加通道成功 $('#channelModal').modal('hide'); } else if(itype==2){ //修改通道成功 currentEditingRow = null; $('#ChangeC').modal('hide'); } } }) .catch((error) => { alert(`Error: ${error.message}`); // 使用 Modal 显示错误信息 // 启用保存按钮 saveButton.disabled = false; return; }) .finally(()=>{ // 隐藏 Spinners spinnerOverlay.style.display = "none"; }); } else { alert('通道名称和RTSP地址不能为空'); } } } //初始化通道管理页面元素数据 async function fetchChannelData() { //获取通道相关信息(/api/channel/list),刷新通道表格控件数据 try { const response = await fetch(apiEndpoint); channelData = await response.json(); channelData_bak = channelData; url = "/api/channel/area/list" area_response = await fetch(url); areaDatas = await area_response.json(); areaData = ["请选择"]; //清空下 areaDatas.forEach((area) => { areaData.push(area.area_name) }); renderTable(); //刷新表格 renderPagination(); //刷新分页元素 renderAreaOptions(); //所属区域下来框 } catch (error) { console.error('Error fetching channel data:', error); } } //刷新表单页面数据 function renderTable() { const tableBody = document.getElementById('table-body'); tableBody.innerHTML = ''; const start = (currentPage - 1) * rowsPerPage; const end = start + rowsPerPage; const pageData = channelData.slice(start, end); const surplus_count = rowsPerPage - pageData.length; pageData.forEach((channel) => { // if(area_name!==channel.area_name){ //这里要求区域名称一样的要在一起 // area_name = channel.area_name; // areaData.push(area_name); // } const row = document.createElement('tr'); row.innerHTML = ` ${channel.ID} ${channel.area_name} ${channel.channel_name} ${channel.ulr} ${channel.model_name} `; tableBody.appendChild(row); row.querySelector('.modify-btn').addEventListener('click', () => modifyChannel(row)); row.querySelector('.algorithm-btn').addEventListener('click', () => configureAlgorithm(row)); row.querySelector('.delete-btn').addEventListener('click', () => deleteChannel(row)); }); } //关键字查询数据 async function performSearch() { try { const area = document.getElementById('areaSelect').value; const channelName = document.getElementById('channelNameInput').value; if(area === "请选择" && channelName===""){ channelData = channelData_bak; } else if(area === "请选择"){ channelData = []; channelData_bak.forEach((channel) => { if(channelName === channel.channel_name){ channelData.push(channel); } }); } else if(channelName === ""){ channelData = []; channelData_bak.forEach((channel) => { if(area === channel.area_name){ channelData.push(channel); } }); } else{ channelData = []; channelData_bak.forEach((channel) => { if(area === channel.area_name && channelName === channel.channel_name){ channelData.push(channel); } }); } // 渲染表格和分页控件 currentPage = 1; // 重置当前页为第一页 renderTable(); renderPagination(); } catch (error) { console.error('Error performing search:', error); } } //点击修改按钮,显示修改通道信息模块 --只是显示 function modifyChannel(row) { // const cid = row.cells[0].innerText; const areaName = row.cells[1].innerText; const channelName = row.cells[2].innerText; const url = row.cells[3].innerText; const area = document.getElementById('areaSelect_CC'); const CName = document.getElementById('CNameInput_cc'); const RTSP = document.getElementById('RTSPInput_cc'); for(let i=0;i< area.options.length;i++){ if(area.options[i].value === areaName){ area.options[i].selected = true; break; } } CName.value = channelName; RTSP.value = url; currentEditingRow = row; $('#ChangeC').modal('show'); } //点击算法按钮,显示算法配置模块 --只是显示 function configureAlgorithm(row) { //获取当前行信息 currentEditingRow = row; const cid = row.cells[0].innerText; //清除数据,若需要的话 ctx.clearRect(0, 0, canvas.width, canvas.height); //清除左侧绘画和画线信息 tbody.innerHTML = ''; //清空布防控件数据 points = []; //清空绘制检测区域 draw_status = false; b_img = false; document.getElementById('but_hzqy').textContent = "绘制区域"; //开始初始化算法管理模块 show_channel_img(cid); //获取并显示一帧图片 -- 获取不到图片就是黑画面 show_channel_model_schedule(cid); //获取并显示结构化数据 //显示窗口 $('#MX_M').modal('show'); } //获取一帧图片 function show_channel_img(cid){ const data = {"cid":cid}; fetch('/api/channel/img', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) .then(response => response.json()) .then(data => { if (data.image) { b_img = true; img.src = 'data:image/jpeg;base64,' + data.image; } else { console.error('Error:', data.error); } }) .catch(error => console.error('Error:', error)); } //图片加载事项 img.onload = () => { //清除、画图和画线应该分开 // 设置画布宽高 backgroundCanvas.width = canvas.width = img.width; backgroundCanvas.height = canvas.height = img.height; // 将图片绘制到背景画布上 backgroundCtx.drawImage(img, 0, 0, img.width, img.height); // 将背景画布的内容复制到前台画布上 ctx.drawImage(backgroundCanvas, 0, 0, canvas.width, canvas.height); //绘制画面 }; //开始和重新绘制 function startDraw(){ if(!document.getElementById('zdjc').checked){ alert("请先选择指定区域!"); return; } let but = document.getElementById('but_hzqy'); if(!draw_status){//开始绘制 if(points.length >0){ if (confirm('开始绘制将清除未提交保存的绘制数据,是否继续?')) { draw_status = true; points = []; //按钮文字调整为结束绘制 but.textContent = '结 束 绘 制'; // 清除前台画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 将背景画布的内容复制到前台画布上 ctx.drawImage(backgroundCanvas, 0, 0, canvas.width, canvas.height); } } else{ draw_status = true; but.textContent = '结束绘制'; } } else{//结束绘制 draw_status = false; but.textContent = '绘制区域'; } } //单选按钮点击事件处理 function handleRadioClick(event) { const selectedRadio = event.target; console.log('Selected Radio:', selectedRadio.id); // 根据选中的单选按钮执行相应操作 if (selectedRadio.id === 'qjjc') { if(draw_status){ alert("请先结束绘制后,再切换检测方案!"); document.getElementById('zdjc').checked = true; } else{ // 处理全画面生效的逻辑 if(points.length>0){ if (!confirm('切换到全画面生效,将清除已绘制的区域信息,是否切换?')) { document.getElementById('zdjc').checked = true; }else{ points = []; } } } //console.log('全画面生效'); } else if (selectedRadio.id === 'zdjc') { // 处理指定区域的逻辑 console.log('指定区域'); } } // 鼠标点击事件处理--动态绘图 canvas.addEventListener('click', (event) => { if(draw_status){ const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; // 获取鼠标相对于canvas的位置 const x = (event.clientX - rect.left) * scaleX; const y = (event.clientY - rect.top) * scaleY; points.push({ x, y }); console.log(points); //绘制线条 drawLines(); } }); // 绘制区域,各点连接 function drawLines() { if(b_img){ // 清除前台画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 将背景画布的内容复制到前台画布上 ctx.drawImage(backgroundCanvas, 0, 0, canvas.width, canvas.height); // 绘制点和线 ctx.strokeStyle = 'red'; ctx.lineWidth = 2; if (points.length > 0) { ctx.beginPath(); ctx.moveTo(points[0].x, points[0].y); for (let i = 1; i < points.length; i++) { ctx.lineTo(points[i].x, points[i].y); } // 连接最后一个点到起点 ctx.lineTo(points[0].x, points[0].y); ctx.stroke(); } points.forEach(point => { ctx.beginPath(); ctx.arc(point.x, point.y, 5, 0, Math.PI * 2); ctx.fillStyle = 'red'; ctx.fill(); }); } } //获取并显示该通道相关算法的结构化数据 --- 这里用GET会更加贴切一些 function show_channel_model_schedule(cid){ //发送视频链接接口 const url = '/api/channel/C2M'; const data = {"cid":cid}; // 发送 POST 请求 fetch(url, { method: 'POST', // 指定请求方法为 POST headers: { 'Content-Type': 'application/json' // 设置请求头,告诉服务器请求体的数据类型为 JSON }, body: JSON.stringify(data) // 将 JavaScript 对象转换为 JSON 字符串 }) .then(response => response.json()) // 将响应解析为 JSON .then(data => { const m_datas = data.m_datas; //算法清单 const c2m_data = data.c2m_data; //该通道管理算法的相关数据,会有空的情况 const schedule = data.schedule; //布防计划 //console.log("m_datas--",m_datas); //console.log("c2m_data--",c2m_data); //console.log("schedule--",schedule); //配置算法下拉清单 select_datas = ["请选择"]; m_datas.forEach(option => { select_datas.push(option.model_name); }); set_select_data("model_select",select_datas); select_str = currentEditingRow.cells[4].innerText; //检测区域 if(c2m_data.length >0){ model_id = c2m_data[0].model_id; model_name = currentEditingRow.cells[4].innerText; set_select_selct("model_select",model_name); check_area = c2m_data[0].check_area if( check_area == 0){ //全画面生效 document.getElementById('qjjc').checked = true; m_polygon = ""; } else{//指定区域 document.getElementById('zdjc').checked = true; m_polygon = c2m_data[0].polygon; console.log("m_polygon--",m_polygon); if(m_polygon !== ""){ //指定区域了,一般是会有数据的。 const coords = parseCoordStr(m_polygon); points = coords; drawLines(); } } //阈值 document.getElementById('zxyz').value = c2m_data[0].conf_thres document.getElementById('iouyz').value = c2m_data[0].iou_thres } //布防计划 const days = ['一', '二', '三', '四', '五', '六','日']; const num_days=['0','1','2','3','4','5','6'] days.forEach((day, dayIndex) => { const row = document.createElement('tr'); const dayCell = document.createElement('th'); dayCell.textContent = day; row.appendChild(dayCell); num_day = num_days[dayIndex] for (let hour = 0; hour < 24; hour++) { const cell = document.createElement('td'); if(schedule.length >0){ const status = schedule.find(item => item.day === num_day && item.hour === hour); if (status && status.status === 1) { cell.classList.add('blocked'); } else { cell.classList.add('allowed'); } } else{ cell.classList.add('blocked'); } row.appendChild(cell); cell.addEventListener('click', () => { if (cell.classList.contains('blocked')) { cell.classList.remove('blocked'); cell.classList.add('allowed'); // Update status in the database //updateStatus(day, hour, 0); } else { cell.classList.remove('allowed'); cell.classList.add('blocked'); // Update status in the database //updateStatus(day, hour, 1); } }); } tbody.appendChild(row); }); }) .catch((error) => { alert(`Error: ${error.message}`); // 使用 Modal 显示错误信息 return; }); } // 将字符串转换为数组 function parseCoordStr(str) { return str.match(/\(([^)]+)\)/g).map(pair => { const [x, y] = pair.replace(/[()]/g, '').split(',').map(Number); return { x, y }; }); } //关闭算法配置窗口 function close_mx_model(){ if (confirm('确定退出窗口吗?未保存的修改将丢失!')) { $('#MX_M').modal('hide'); } } //保存算法配置窗口数据 function save_mx_model(){ let model_name; //算法名称 let check_area; //检测区域标识 0-全局,1-指定范围 let polygon_str; //具体的检测区域 let conf_thres; //置信阈值 let iou_thres; //iou阈值 let schedule; //布防计划 const saveButton = document.getElementById('saveButton_mx'); saveButton.disabled = true; //不可点击状态 //配置算法 model_name = document.getElementById("model_select").value; //检测区域 if(document.getElementById('zdjc').checked){ check_area = 1; console.log("points--",points); if (points.length > 0){ const formattedArray = points.map(point => `(${point.x},${point.y})`); polygon_str = `[${formattedArray.join(',')}]`; }else{ polygon_str = ""; } }else{ check_area = 0; polygon_str = ""; } //置信阈值和IOU阈值 conf_thres = getInputValueAsFloat('zxyz'); iou_thres = getInputValueAsFloat('iouyz'); //验证数据 if(model_name !== "请选择"){ console.log(model_name); if(conf_thres <= 0 || conf_thres>=1 || iou_thres <= 0 || iou_thres>=1){ alert("阈值的有效范围是大于0,小于1;请输入正确的阈值(默认可0.5)。"); saveButton.disabled = false; //不可点击状态 return; } } //布防计划 // 定义一个对象来存储数据 const scheduleData = { '0': Array(24).fill(0), '1': Array(24).fill(0), '2': Array(24).fill(0), '3': Array(24).fill(0), '4': Array(24).fill(0), '5': Array(24).fill(0), '6': Array(24).fill(0) }; // 遍历 tbody 的每一行 [...tbody.children].forEach((row, dayIndex) => { // 获取当前行的所有单元格 const cells = row.getElementsByTagName('td'); // 遍历每一个单元格 for (let hour = 0; hour < cells.length; hour++) { // 检查单元格的 class 是否包含 'blocked' if (cells[hour].classList.contains('blocked')) { // 将对应的 scheduleData 位置设置为 1 scheduleData[dayIndex][hour] = 1; } else { // 将对应的 scheduleData 位置设置为 0 scheduleData[dayIndex][hour] = 0; } } }); // 将 scheduleData 对象转换为 JSON 字符串 const scheduleData_json = JSON.stringify(scheduleData); //提交到服务器 // console.log("model_name--",model_name); // console.log("check_area--",check_area); // console.log("polygon_str--",polygon_str); // console.log("iou_thres--",iou_thres); // console.log("conf_thres--",conf_thres); // console.log("schedule-- ",scheduleData_json); cid = currentEditingRow.cells[0].innerText; const url = '/api/channel/chanegeC2M'; const data = {"model_name":model_name,"check_area":check_area,"polygon_str":polygon_str,"iou_thres":iou_thres, "conf_thres":conf_thres,"schedule":scheduleData_json,"cid":cid}; // 发送 POST 请求 fetch(url, { method: 'POST', // 指定请求方法为 POST headers: { 'Content-Type': 'application/json' // 设置请求头,告诉服务器请求体的数据类型为 JSON }, body: JSON.stringify(data) // 将 JavaScript 对象转换为 JSON 字符串 }) .then(response => response.json()) // 将响应解析为 JSON .then(data => { const istatus = data.status; if(istatus === 0){ alert(data.msg); // 使用 Modal 显示消息 // 启用保存按钮 saveButton.disabled = false; return; } else{ // 启用保存按钮 saveButton.disabled = false; //刷新列表 fetchChannelData(); //$('#MX_M').modal('hide'); alert("修改成功!"); } }) .catch((error) => { alert(`Error: ${error.message}`); // 启用保存按钮 saveButton.disabled = false; return; }); } //删除通道 function deleteChannel(row) { if (confirm('确定删除此通道吗?')) { cid = row.cells[0].innerText; //发送视频链接接口 const url = '/api/channel/del'; const data = {"cid":cid}; // 发送 POST 请求 fetch(url, { method: 'POST', // 指定请求方法为 POST headers: { 'Content-Type': 'application/json' // 设置请求头,告诉服务器请求体的数据类型为 JSON }, body: JSON.stringify(data) // 将 JavaScript 对象转换为 JSON 字符串 }) .then(response => response.json()) // 将响应解析为 JSON .then(data => { const istatus = data.status; if(istatus === 0){ alert(data.msg); // 使用 Modal 显示消息 return; } else{ //刷新列表 row.remove(); alert("删除通道成功!"); } }) .catch((error) => { alert(`Error: ${error.message}`); // 使用 Modal 显示错误信息 return; }); } } //刷新分页标签 function renderPagination() { const pagination = document.getElementById('pagination'); pagination.innerHTML = ''; const totalPages = Math.ceil(channelData.length / rowsPerPage); for (let i = 1; i <= totalPages; i++) { const pageItem = document.createElement('li'); pageItem.className = 'page-item' + (i === currentPage ? ' active' : ''); pageItem.innerHTML = `${i}`; pageItem.addEventListener('click', (event) => { event.preventDefault(); currentPage = i; renderTable(); renderPagination(); }); pagination.appendChild(pageItem); } } //刷新区域下拉控件 function renderAreaOptions() { const areaSelect = document.getElementById('areaSelect'); const areaSelect_M = document.getElementById('areaSelect_M') const areaSelect_CC = document.getElementById('areaSelect_CC') //先清空 areaSelect.innerHTML = ''; areaSelect_M.innerHTML = ''; areaSelect_CC.innerHTML = ''; //再添加 areaData.forEach(option => { const optionElement = document.createElement('option'); optionElement.textContent = option; areaSelect.appendChild(optionElement); const optionElement_m = document.createElement('option'); optionElement_m.textContent = option; areaSelect_M.appendChild(optionElement_m); const optionElement_cc = document.createElement('option'); optionElement_cc.textContent = option; areaSelect_CC.appendChild(optionElement_cc); }); }