@ -1,5 +1,5 @@ |
|||||
from quart import Blueprint |
from quart import Blueprint |
||||
|
|
||||
main = Blueprint('main', __name__,static_folder='static/resources',template_folder='templates') |
main = Blueprint('main', __name__,template_folder='templates') |
||||
|
|
||||
from . import routes |
from . import routes |
||||
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 1.5 KiB |
@ -1,15 +0,0 @@ |
|||||
.form-control-dark { |
|
||||
border-color: var(--bs-gray); |
|
||||
} |
|
||||
.form-control-dark:focus { |
|
||||
border-color: #fff; |
|
||||
box-shadow: 0 0 0 .25rem rgba(255, 255, 255, .25); |
|
||||
} |
|
||||
|
|
||||
.text-small { |
|
||||
font-size: 85%; |
|
||||
} |
|
||||
|
|
||||
.dropdown-toggle { |
|
||||
outline: 0; |
|
||||
} |
|
@ -1,33 +0,0 @@ |
|||||
html, |
|
||||
body { |
|
||||
height: 100%; |
|
||||
} |
|
||||
|
|
||||
body { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
padding-top: 40px; |
|
||||
padding-bottom: 40px; |
|
||||
background-color: #f5f5f5; |
|
||||
} |
|
||||
|
|
||||
.form-signin { |
|
||||
max-width: 400px; |
|
||||
padding: 15px; |
|
||||
} |
|
||||
|
|
||||
.form-signin .form-floating:focus-within { |
|
||||
z-index: 2; |
|
||||
} |
|
||||
|
|
||||
.form-signin input[type="text"] { |
|
||||
margin-bottom: 5px; |
|
||||
border-bottom-right-radius: 0; |
|
||||
border-bottom-left-radius: 0; |
|
||||
} |
|
||||
|
|
||||
.form-signin input[type="password"] { |
|
||||
margin-bottom: 10px; |
|
||||
border-top-left-radius: 0; |
|
||||
border-top-right-radius: 0; |
|
||||
} |
|
Before Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 1.5 KiB |
@ -1,47 +0,0 @@ |
|||||
|
|
||||
document.addEventListener('DOMContentLoaded', function() { |
|
||||
const links = document.querySelectorAll('.nav-pills .nav-link'); |
|
||||
// Get the current page path
|
|
||||
const currentPath = window.location.pathname; |
|
||||
links.forEach(link => { |
|
||||
// Compare the link's href with the current path
|
|
||||
if (link.href.endsWith(currentPath)) { |
|
||||
link.classList.add('active'); |
|
||||
} else { |
|
||||
link.classList.remove('active'); |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
function showModal(message) { |
|
||||
// 设置 Modal 中的消息
|
|
||||
document.getElementById('modalMessage').innerText = message; |
|
||||
|
|
||||
// 显示 Modal
|
|
||||
const responseModal = new bootstrap.Modal(document.getElementById('responseModal')); |
|
||||
responseModal.show(); |
|
||||
} |
|
||||
|
|
||||
//动态填充select控件
|
|
||||
function set_select_data(select_ele_id,datas){ |
|
||||
const select_Ele = document.getElementById(select_ele_id); |
|
||||
//清空老数据
|
|
||||
select_Ele.innerHTML = ''; |
|
||||
//添加列表
|
|
||||
datas.forEach(option => { |
|
||||
const optionElement = document.createElement('option'); |
|
||||
optionElement.textContent = option; |
|
||||
select_Ele.appendChild(optionElement); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
//设定选项选中状态
|
|
||||
function set_select_selct(select_ele_id,option_str){ |
|
||||
const select_Ele = document.getElementById(select_ele_id); |
|
||||
for(let i=0;i< select_Ele.options.length;i++){ |
|
||||
if(select_Ele.options[i].value === option_str){ |
|
||||
select_Ele.options[i].selected = true; |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,608 +0,0 @@ |
|||||
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; |
|
||||
if(itype ==1){ |
|
||||
const saveButton = document.getElementById('saveButton'); |
|
||||
const CNameInput = document.getElementById('CNameInput'); |
|
||||
const RTSPInput = document.getElementById('RTSPInput'); |
|
||||
area = document.getElementById('areaSelect_M').value; |
|
||||
cName = CNameInput.value.trim(); |
|
||||
Rtsp = RTSPInput.value.trim(); |
|
||||
cid = -1 |
|
||||
} |
|
||||
else if(itype ==2){ |
|
||||
const saveButton = document.getElementById('saveButton_cc'); |
|
||||
const CNameInput = document.getElementById('CNameInput_cc'); |
|
||||
const RTSPInput = document.getElementById('RTSPInput_cc'); |
|
||||
area = document.getElementById('areaSelect_CC').value; |
|
||||
cName = CNameInput.value.trim(); |
|
||||
Rtsp = RTSPInput.value.trim(); |
|
||||
cid = currentEditingRow.cells[0].innerText; |
|
||||
} |
|
||||
|
|
||||
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}; |
|
||||
// 发送 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(); |
|
||||
if(itype ==1){ |
|
||||
//添加通道成功
|
|
||||
$('#channelModal').modal('hide'); |
|
||||
alert("添加通道成功!"); // 使用 Modal 显示消息
|
|
||||
} |
|
||||
else if(itype==2){ |
|
||||
//修改通道成功
|
|
||||
currentEditingRow = null; |
|
||||
$('#ChangeC').modal('hide'); |
|
||||
alert("修改通道成功!"); // 使用 Modal 显示消息
|
|
||||
} |
|
||||
} |
|
||||
}) |
|
||||
.catch((error) => { |
|
||||
alert(`Error: ${error.message}`); // 使用 Modal 显示错误信息
|
|
||||
// 启用保存按钮
|
|
||||
saveButton.disabled = false; |
|
||||
return; |
|
||||
}); |
|
||||
} else { |
|
||||
alert('通道名称和RTSP地址不能为空'); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async function fetchChannelData() { //刷新通道数据
|
|
||||
try { |
|
||||
const response = await fetch(apiEndpoint); |
|
||||
channelData = await response.json(); |
|
||||
channelData_bak = channelData; |
|
||||
renderTable(); //读取通道list接口,刷新表格
|
|
||||
renderPagination(); //刷新分页元素
|
|
||||
renderAreaOptions(); //所属区域下来框
|
|
||||
} catch (error) { |
|
||||
console.error('Error fetching channel data:', error); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
//关键字查询数据
|
|
||||
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 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; |
|
||||
let area_name = ""; |
|
||||
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 = ` |
|
||||
<td>${channel.ID}</td> |
|
||||
<td>${channel.area_name}</td> |
|
||||
<td>${channel.channel_name}</td> |
|
||||
<td>${channel.ulr}</td> |
|
||||
<td>${channel.model_name}</td> |
|
||||
<td> |
|
||||
<button class="btn btn-primary btn-sm modify-btn">修改</button> |
|
||||
<button class="btn btn-secondary btn-sm algorithm-btn">算法</button> |
|
||||
<button class="btn btn-danger btn-sm delete-btn">删除</button> |
|
||||
</td> |
|
||||
`;
|
|
||||
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)); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
//修改通道信息
|
|
||||
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') { |
|
||||
console.log("points.length",points.length); |
|
||||
// 处理全画面生效的逻辑
|
|
||||
if(points.length>0){ |
|
||||
if (!confirm('已经绘制了检测区域,确认要切换到全画面生效吗?')) { |
|
||||
document.getElementById('zdjc').checked = true; |
|
||||
} |
|
||||
} |
|
||||
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 }); |
|
||||
//绘制线条
|
|
||||
drawLines(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
//初始化读取该视频通道与算法配置的相关信息 --- 这里用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; |
|
||||
if(m_polygon !== ""){ //指定区域了,一般是会有数据的。
|
|
||||
const coords = parseCoordStr(m_polygon); |
|
||||
points = coords; |
|
||||
drawLines(); |
|
||||
} |
|
||||
} |
|
||||
console.log("m_polygon",m_polygon); |
|
||||
//阈值
|
|
||||
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 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(); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
//关闭算法配置窗口
|
|
||||
function close_mx_model(){ |
|
||||
if (confirm('确定退出窗口吗?未保存的修改将丢失!')) { |
|
||||
$('#MX_M').modal('hide'); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
//保存算法配置窗口数据
|
|
||||
function save_mx_model(){ |
|
||||
//?
|
|
||||
currentEditingRow =null; |
|
||||
} |
|
||||
|
|
||||
//删除通道
|
|
||||
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 显示消息
|
|
||||
// 启用保存按钮
|
|
||||
saveButton.disabled = false; |
|
||||
return; |
|
||||
} |
|
||||
else{ |
|
||||
// 启用保存按钮
|
|
||||
saveButton.disabled = false; |
|
||||
//刷新列表
|
|
||||
row.remove(); |
|
||||
alert("删除通道成功!"); |
|
||||
} |
|
||||
}) |
|
||||
.catch((error) => { |
|
||||
alert(`Error: ${error.message}`); // 使用 Modal 显示错误信息
|
|
||||
// 启用保存按钮
|
|
||||
saveButton.disabled = false; |
|
||||
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 = `<a class="page-link" href="#">${i}</a>`; |
|
||||
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') |
|
||||
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); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
@ -1,46 +0,0 @@ |
|||||
<!DOCTYPE html> |
|
||||
<html lang="en"> |
|
||||
<head> |
|
||||
<meta charset="UTF-8"> |
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
||||
<title>{% block title %}My Website{% endblock %}</title> |
|
||||
<link rel="stylesheet" href="{{ url_for('main.static', filename='css/bootstrap.min.css') }}"> |
|
||||
<link href="{{ url_for('main.static', filename='css/headers.css') }}" rel="stylesheet"> |
|
||||
<style> |
|
||||
.bi { |
|
||||
vertical-align: -.125em; |
|
||||
fill: currentColor; |
|
||||
} |
|
||||
{% block style %}{% endblock %} |
|
||||
|
|
||||
</style> |
|
||||
</head> |
|
||||
<body> |
|
||||
{% include 'header.html' %} |
|
||||
<!-- Modal Structure --> |
|
||||
<div class="modal fade" id="responseModal" tabindex="-1" aria-labelledby="responseModalLabel" aria-hidden="true"> |
|
||||
<div class="modal-dialog"> |
|
||||
<div class="modal-content"> |
|
||||
<div class="modal-header"> |
|
||||
<h3 class="modal-title" id="responseModalLabel">提示信息</h3> |
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
|
||||
</div> |
|
||||
<div class="modal-body" id="modalMessage"> |
|
||||
<!-- The message from the server will be inserted here --> |
|
||||
</div> |
|
||||
<div class="modal-footer"> |
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<main class="p-0 mb-0"> |
|
||||
{% block content %}{% endblock %} |
|
||||
</main> |
|
||||
{% include 'footer.html' %} |
|
||||
<script src="{{ url_for('main.static', filename='scripts/bootstrap.bundle.min.js') }}"></script> |
|
||||
<script src="{{ url_for('main.static', filename='scripts/base.js') }}"></script> |
|
||||
{% block script %}{% endblock %} |
|
||||
|
|
||||
</body> |
|
||||
</html> |
|
@ -1,291 +0,0 @@ |
|||||
{% extends 'base.html' %} |
|
||||
|
|
||||
{% block title %}ZFBOX{% endblock %} |
|
||||
|
|
||||
{% block style %} |
|
||||
.pagination { |
|
||||
justify-content: flex-end; /* 右对齐 */ |
|
||||
} |
|
||||
.page-item .page-link { |
|
||||
padding: 0.25rem 0.5rem; /* 缩小按钮 */ |
|
||||
font-size: 0.875rem; /* 调整字体大小 */ |
|
||||
} |
|
||||
.btn-group-sm > .btn, .btn-sm { |
|
||||
padding: 0.25rem 0.5rem; |
|
||||
font-size: 0.875rem; |
|
||||
line-height: 1.5; |
|
||||
} |
|
||||
.btn-group-sm .btn { |
|
||||
margin-right: 5px; /* 按钮之间的间距 */ |
|
||||
} |
|
||||
.form-group-right h5 { |
|
||||
text-align: right; |
|
||||
margin-bottom: 0; |
|
||||
} |
|
||||
.table-container { |
|
||||
min-height: 400px; /* 设置最小高度,可以根据需要调整 */ |
|
||||
} |
|
||||
|
|
||||
.video-area { |
|
||||
width: 100%; |
|
||||
position: relative; |
|
||||
background-color: #000; |
|
||||
border: 1px solid #ddd; |
|
||||
} |
|
||||
|
|
||||
.video-area::before { |
|
||||
content: ""; |
|
||||
display: block; |
|
||||
padding-top: 75%; /* 4:3 ratio */ |
|
||||
} |
|
||||
|
|
||||
.video-area canvas { |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
left: 0; |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
object-fit: contain; |
|
||||
} |
|
||||
.schedule-table { |
|
||||
width: 100%; |
|
||||
table-layout: fixed; |
|
||||
} |
|
||||
.schedule-table th, .schedule-table td { |
|
||||
text-align: center; |
|
||||
vertical-align: middle; |
|
||||
cursor: pointer; |
|
||||
border: 1px solid #dee2e6; |
|
||||
padding: 8px; |
|
||||
} |
|
||||
.schedule-table td.allowed { |
|
||||
background-color: white; |
|
||||
} |
|
||||
.schedule-table td.blocked { |
|
||||
background-color: blue; |
|
||||
} |
|
||||
/* 缩小表格行高 */ |
|
||||
.table-sm th, |
|
||||
.table-sm td { |
|
||||
padding: 0.2rem; /* 调整这里的值来改变行高 */ |
|
||||
} |
|
||||
canvas { |
|
||||
border: 1px solid black; |
|
||||
} |
|
||||
{% endblock %} |
|
||||
|
|
||||
{% block content %} |
|
||||
<div class="container d-flex flex-column" > |
|
||||
<div class="row mb-3 d-flex justify-content-center align-items-center "> |
|
||||
<div class="col"><h5 class="form-group-right">所属区域:</h5></div> |
|
||||
<div class="col-3 mr-2"> |
|
||||
<select id="areaSelect" class="form-select mr-2" aria-label="Default select example"> |
|
||||
<!-- 数据动态填充 --> |
|
||||
</select></div> |
|
||||
<div class="col"><h5 class="form-group-right">通道名称:</h5></div> |
|
||||
<div class="col-5 mr-2"> |
|
||||
<input id="channelNameInput" type="text" class="form-control mr-2" placeholder="Channel_name" aria-label="Channel_name"></div> |
|
||||
<div class="col-1"><button id="searchButton" type="button" class="btn btn-primary">查 询</button></div> |
|
||||
</div> |
|
||||
<div class="mb-3"> |
|
||||
<!-- <button id="manageAreaButton" type="button" class="btn btn-primary mr-2">区域管理</button>--> |
|
||||
<button id="addChannelButton" type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#channelModal"> |
|
||||
新增通道 |
|
||||
</button> |
|
||||
</div> |
|
||||
<!-- 新增通道模态框 --> |
|
||||
<div class="modal fade" id="channelModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="add_channel" aria-hidden="true"> |
|
||||
<div class="modal-dialog"> |
|
||||
<div class="modal-content"> |
|
||||
<div class="modal-header"> |
|
||||
<h5 class="modal-title" id="channelModalLabel">新增通道</h5> |
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
|
||||
</div> |
|
||||
<div class="modal-body"> |
|
||||
<div class="form-group"> |
|
||||
<label for="areaSelect_M">所属区域:</label> |
|
||||
<select id="areaSelect_M" class="form-select" aria-label="D"> |
|
||||
<!-- 数据动态填充 --> |
|
||||
</select> |
|
||||
</div> |
|
||||
<div class="form-group"> |
|
||||
<label for="CNameInput">通道名称:</label> |
|
||||
<input type="text" class="form-control" id="CNameInput"> |
|
||||
</div> |
|
||||
<div class="form-group"> |
|
||||
<label for="RTSPInput">RTSP地址:</label> |
|
||||
<input type="text" class="form-control" id="RTSPInput"> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="modal-footer"> |
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button> |
|
||||
<button type="button" class="btn btn-primary" id="saveButton">保存</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<!-- 修改通道模态框 --> |
|
||||
<div class="modal fade" id="ChangeC" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="change_c" aria-hidden="true"> |
|
||||
<div class="modal-dialog"> |
|
||||
<div class="modal-content"> |
|
||||
<div class="modal-header"> |
|
||||
<h5 class="modal-title" id="ChangeChannel">修改通道</h5> |
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
|
||||
</div> |
|
||||
<div class="modal-body"> |
|
||||
<div class="form-group"> |
|
||||
<label for="areaSelect_CC">所属区域:</label> |
|
||||
<select id="areaSelect_CC" class="form-select" aria-label="D"> |
|
||||
<!-- 数据动态填充 --> |
|
||||
</select> |
|
||||
</div> |
|
||||
<div class="form-group"> |
|
||||
<label for="CNameInput_cc">通道名称:</label> |
|
||||
<input type="text" class="form-control" id="CNameInput_cc"> |
|
||||
</div> |
|
||||
<div class="form-group"> |
|
||||
<label for="RTSPInput_cc">RTSP地址:</label> |
|
||||
<input type="text" class="form-control" id="RTSPInput_cc"> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="modal-footer"> |
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button> |
|
||||
<button type="button" class="btn btn-primary" id="saveButton_cc">保存</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<!-- 算法管理模态框 --> |
|
||||
<div class="modal fade" id="MX_M" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="m_mx" aria-hidden="true"> |
|
||||
<div class="modal-dialog modal-lg"> |
|
||||
<div class="modal-content"> |
|
||||
<div class="modal-header"> |
|
||||
<h5 class="modal-title" id="MX_Title">配置算法</h5> |
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> |
|
||||
</div> |
|
||||
<div class="modal-body"> |
|
||||
<div class="container-fluid"> |
|
||||
<div class="row align-items-center"> |
|
||||
<div class="col-6"> |
|
||||
<div class="video-area"> |
|
||||
<!-- <img id="video-mx" alt="Video Stream" />--> |
|
||||
<canvas id="backgroundCanvas" style="display: none;"></canvas> |
|
||||
<canvas id="myCanvas"></canvas> |
|
||||
</div> |
|
||||
|
|
||||
</div> |
|
||||
<div class="col-6 ms-auto"> |
|
||||
<!-- 配置算法 --> |
|
||||
<div class="row align-items-center mb-2"> |
|
||||
<div class="col-3"><label for="model_select">配置算法:</label></div> |
|
||||
<div class="col-9"> |
|
||||
<select id="model_select" class="form-select" aria-label="D"> |
|
||||
<!-- 数据动态填充 --> |
|
||||
</select> |
|
||||
</div> |
|
||||
</div> |
|
||||
<!-- 检测区域 --> |
|
||||
<div class="row align-items-center mb-2"> |
|
||||
<div class="col-3"><label for="model_select">检测区域:</label></div> |
|
||||
<div class="col-9"> |
|
||||
<div class="row"> |
|
||||
<div class="col-6"> |
|
||||
<div class="form-check"> |
|
||||
<input class="form-check-input" type="radio" name="jcqy" |
|
||||
id="qjjc" checked onclick="handleRadioClick(event)"> |
|
||||
<label class="form-check-label" for="qjjc">全画面生效</label> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="col-6"> |
|
||||
<div class="form-check"> |
|
||||
<input class="form-check-input" type="radio" name="jcqy" |
|
||||
id="zdjc" onclick="handleRadioClick(event)"> |
|
||||
<label class="form-check-label" for="zdjc">指定区域</label> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="row"> |
|
||||
<button type="button" class="btn btn-primary" id="but_hzqy" style="--bs-btn-padding-y:.20rem; --bs-btn-font-size: .70rem;"> |
|
||||
绘制区域</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<!-- 检测阈值 --> |
|
||||
<div class="row align-items-center mb-2"> |
|
||||
<div class="col-3"> |
|
||||
<label>置信阈值:</label> |
|
||||
</div> |
|
||||
<div class="col-3"> |
|
||||
<input type="text" class="form-control" id="zxyz"> |
|
||||
</div> |
|
||||
<div class="col-3"> |
|
||||
<label>IOU阈值:</label> |
|
||||
</div> |
|
||||
<div class="col-3"> |
|
||||
<input type="text" class="form-control" id="iouyz"> |
|
||||
</div> |
|
||||
</div> |
|
||||
<!-- 布放计划 --> |
|
||||
<div class="row form-group"> |
|
||||
<label for="11">布放计划:</label> |
|
||||
<div id="11"> |
|
||||
<table class="schedule-table table table-sm table-bordered" style="font-size: 7px;"> |
|
||||
<thead> |
|
||||
<tr> |
|
||||
<th></th><th>00</th><th>01</th><th>02</th><th>03</th><th>04</th> |
|
||||
<th>05</th><th>06</th><th>07</th><th>08</th><th>09</th><th>10</th> |
|
||||
<th>11</th><th>12</th><th>13</th><th>14</th><th>15</th><th>16</th> |
|
||||
<th>17</th><th>18</th><th>19</th><th>20</th><th>21</th><th>22</th> |
|
||||
<th>23</th> |
|
||||
</tr> |
|
||||
</thead> |
|
||||
<tbody id="schedule-body"> |
|
||||
<!-- 表格数据将由JavaScript动态填充 --> |
|
||||
</tbody> |
|
||||
</table> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="modal-footer"> |
|
||||
<button type="button" class="btn btn-secondary" id="cancelButton_mx">取消</button> |
|
||||
<button type="button" class="btn btn-primary" id="saveButton_mx">保存</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div class="table-container"> |
|
||||
<table class="table"> |
|
||||
<thead class="table-light"> |
|
||||
<tr> |
|
||||
<th scope="col">ID</th> |
|
||||
<th scope="col">所属区域</th> |
|
||||
<th scope="col">通道名称</th> |
|
||||
<th scope="col">RTSP地址</th> |
|
||||
<th scope="col">配置算法</th> |
|
||||
<th scope="col">操作</th> |
|
||||
</tr> |
|
||||
</thead> |
|
||||
<tbody id="table-body" class="table-group-divider"> |
|
||||
<!-- 表格数据动态填充 --> |
|
||||
</tbody> |
|
||||
</table> |
|
||||
<nav> |
|
||||
<ul id="pagination" class="pagination"> |
|
||||
<!-- 分页控件将动态生成 --> |
|
||||
</ul> |
|
||||
</nav> |
|
||||
</div> |
|
||||
</div> |
|
||||
{% endblock %} |
|
||||
|
|
||||
{% block script %} |
|
||||
<script src="{{ url_for('main.static', filename='scripts/jquery-3.2.1.slim.min.js') }}"></script> |
|
||||
<script src="{{ url_for('main.static', filename='scripts/popper.min.js') }}"></script> |
|
||||
<script src="{{ url_for('main.static', filename='scripts/channel_manager.js') }}"></script> |
|
||||
{% endblock %} |
|
Before Width: | Height: | Size: 3.8 KiB |
@ -1,35 +0,0 @@ |
|||||
<header class="p-3 mb-3 border-bottom"> |
|
||||
<div class="container"> |
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start"> |
|
||||
|
|
||||
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none"> |
|
||||
<img src="images/登录/zf.svg" alt="" width="40" height="40"> |
|
||||
<!-- <svg class="bi me-2" width="40" height="32"><use xlink:href="#bootstrap"/></svg>--> |
|
||||
<span class="fs-4">ZF_BOX</span> |
|
||||
</a> |
|
||||
|
|
||||
<ul class="nav nav-pills"> |
|
||||
<li class="nav-item"><a href="/view_main.html" class="nav-link active" aria-current="page">实时预览</a></li> |
|
||||
<li class="nav-item"><a href="/channel_manager.html" class="nav-link">通道管理</a></li> |
|
||||
<li class="nav-item"><a href="/schedule.html" class="nav-link">算法管理</a></li> |
|
||||
<li class="nav-item"><a href="#" class="nav-link">系统管理</a></li> |
|
||||
<li class="nav-item"><a href="#" class="nav-link">用户管理</a></li> |
|
||||
</ul> |
|
||||
|
|
||||
<div class="dropdown text-end"> |
|
||||
<a href="#" class="d-block link-dark text-decoration-none dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"> |
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-person-bounding-box" viewBox="0 0 16 16"> |
|
||||
<path d="M1.5 1a.5.5 0 0 0-.5.5v3a.5.5 0 0 1-1 0v-3A1.5 1.5 0 0 1 1.5 0h3a.5.5 0 0 1 0 1h-3zM11 .5a.5.5 0 0 1 .5-.5h3A1.5 1.5 0 0 1 16 1.5v3a.5.5 0 0 1-1 0v-3a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 1-.5-.5zM.5 11a.5.5 0 0 1 .5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 1 0 1h-3A1.5 1.5 0 0 1 0 14.5v-3a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v3a1.5 1.5 0 0 1-1.5 1.5h-3a.5.5 0 0 1 0-1h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 1 .5-.5z"/> |
|
||||
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm8-9a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/> |
|
||||
</svg> |
|
||||
<!-- <img src="https://github.com/mdo.png" alt="mdo" width="32" height="32" class="rounded-circle">--> |
|
||||
</a> |
|
||||
<ul class="dropdown-menu text-small"> |
|
||||
<li><a class="dropdown-item" href="#">修改密码</a></li> |
|
||||
<li><hr class="dropdown-divider"></li> |
|
||||
<li><a class="dropdown-item" href="#">退 出</a></li> |
|
||||
</ul> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</header> |
|
@ -1,176 +1,45 @@ |
|||||
<!DOCTYPE html> |
<!DOCTYPE html> |
||||
<html lang="en"> |
<html> |
||||
<head> |
<head> |
||||
<meta charset="UTF-8"> |
<title>WebRTC Video Stream</title> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
||||
<title>ZFBOX</title> |
|
||||
<link rel="stylesheet" href="{{ url_for('main.static', filename='css/bootstrap.min.css') }}"> |
|
||||
<link href="../static/resources/css/bootstrap.min.css" rel="stylesheet"> |
|
||||
<style> |
|
||||
body { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
height: 100vh; |
|
||||
} |
|
||||
|
|
||||
header { |
|
||||
background-color: #007bff; |
|
||||
color: white; |
|
||||
padding: 10px 0; |
|
||||
} |
|
||||
|
|
||||
.navbar-nav .nav-link { |
|
||||
color: white; |
|
||||
} |
|
||||
|
|
||||
main { |
|
||||
display: flex; |
|
||||
flex: 1; |
|
||||
overflow: hidden; |
|
||||
} |
|
||||
|
|
||||
.tree-view { |
|
||||
border-right: 1px solid #ddd; |
|
||||
padding: 10px; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
|
|
||||
.video-content { |
|
||||
padding: 10px; |
|
||||
overflow-y: auto; |
|
||||
flex: 1; |
|
||||
} |
|
||||
|
|
||||
footer { |
|
||||
background-color: #f8f9fa; |
|
||||
text-align: center; |
|
||||
padding: 10px 0; |
|
||||
} |
|
||||
|
|
||||
.video-frame { |
|
||||
background-color: #f8f9fa; |
|
||||
border: 1px solid #ddd; |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
|
|
||||
.video-frame img { |
|
||||
width: 100%; |
|
||||
height: auto; |
|
||||
} |
|
||||
|
|
||||
.video-grid { |
|
||||
display: grid; |
|
||||
grid-template-columns: repeat(2, 1fr); |
|
||||
gap: 10px; |
|
||||
} |
|
||||
|
|
||||
.video-grid.eight { |
|
||||
grid-template-columns: repeat(4, 1fr); |
|
||||
} |
|
||||
|
|
||||
.toggle-buttons { |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
</style> |
|
||||
</head> |
</head> |
||||
<body> |
<body> |
||||
<header> |
<h1>WebRTC Video Stream</h1> |
||||
<nav class="navbar navbar-expand-lg navbar-light"> |
<video id="video" autoplay playsinline></video> |
||||
<div class="container-fluid"> |
|
||||
<a class="navbar-brand" href="#">智凡BOX</a> |
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> |
|
||||
<span class="navbar-toggler-icon"></span> |
|
||||
</button> |
|
||||
<div class="collapse navbar-collapse" id="navbarNav"> |
|
||||
<ul class="navbar-nav"> |
|
||||
<li class="nav-item"> |
|
||||
<a class="nav-link" href="#">实时预览</a> |
|
||||
</li> |
|
||||
<li class="nav-item"> |
|
||||
<a class="nav-link" href="#">通道管理</a> |
|
||||
</li> |
|
||||
<li class="nav-item"> |
|
||||
<a class="nav-link" href="#">算法管理</a> |
|
||||
</li> |
|
||||
<li class="nav-item"> |
|
||||
<a class="nav-link" href="#">系统管理</a> |
|
||||
</li> |
|
||||
<li class="nav-item"> |
|
||||
<a class="nav-link" href="#">用户管理</a> |
|
||||
</li> |
|
||||
</ul> |
|
||||
<ul class="navbar-nav ms-auto"> |
|
||||
<li class="nav-item"> |
|
||||
<a class="nav-link" href="#">张三 退出</a> |
|
||||
</li> |
|
||||
</ul> |
|
||||
</div> |
|
||||
</div> |
|
||||
</nav> |
|
||||
</header> |
|
||||
<main> |
|
||||
<div class="tree-view col-md-3"> |
|
||||
<ul class="list-group"> |
|
||||
<li class="list-group-item">一区 |
|
||||
<ul class="list-group"> |
|
||||
<li class="list-group-item">北门通道一</li> |
|
||||
<li class="list-group-item">南门通道二</li> |
|
||||
<li class="list-group-item">通道三</li> |
|
||||
</ul> |
|
||||
</li> |
|
||||
<li class="list-group-item">二区域 |
|
||||
<ul class="list-group"> |
|
||||
<li class="list-group-item">通道一</li> |
|
||||
</ul> |
|
||||
</li> |
|
||||
</ul> |
|
||||
</div> |
|
||||
<div class="video-content col-md-9"> |
|
||||
<div class="toggle-buttons"> |
|
||||
<button id="fourView" class="btn btn-primary">四画面</button> |
|
||||
<button id="eightView" class="btn btn-secondary">八画面</button> |
|
||||
</div> |
|
||||
<div id="videoGrid" class="video-grid"> |
|
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
|
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
|
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
|
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</main> |
|
||||
<footer> |
|
||||
© 2024 ZFKJ All Rights Reserved |
|
||||
</footer> |
|
||||
|
|
||||
<script src="{{ url_for('main.static', filename='js/bootstrap.bundle.min.js') }}"></script> |
|
||||
<script> |
<script> |
||||
document.getElementById('fourView').addEventListener('click', function() { |
const pc = new RTCPeerConnection(); |
||||
const videoGrid = document.getElementById('videoGrid'); |
const video = document.getElementById('video'); |
||||
videoGrid.classList.remove('eight'); |
|
||||
videoGrid.classList.add('four'); |
pc.ontrack = (event) => { |
||||
videoGrid.innerHTML = ` |
video.srcObject = event.streams[0]; |
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
}; |
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
|
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
const ws = new WebSocket('ws://' + window.location.host + '/api/ws'); |
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
|
||||
`; |
ws.onmessage = async (event) => { |
||||
}); |
const data = JSON.parse(event.data); |
||||
|
await pc.setRemoteDescription(new RTCSessionDescription(data)); |
||||
document.getElementById('eightView').addEventListener('click', function() { |
|
||||
const videoGrid = document.getElementById('videoGrid'); |
if (data.type === 'offer') { |
||||
videoGrid.classList.remove('four'); |
const answer = await pc.createAnswer(); |
||||
videoGrid.classList.add('eight'); |
await pc.setLocalDescription(answer); |
||||
videoGrid.innerHTML = ` |
ws.send(JSON.stringify({ |
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
'sdp': pc.localDescription.sdp, |
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
'type': pc.localDescription.type |
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
})); |
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
} |
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
}; |
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
|
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
ws.onopen = async () => { |
||||
<div class="video-frame"><img src="../static/resources/images/video_placeholder.png" alt="Video Stream"></div> |
const stream = await navigator.mediaDevices.getUserMedia({ video: true }); |
||||
`; |
stream.getTracks().forEach(track => pc.addTrack(track, stream)); |
||||
}); |
const offer = await pc.createOffer(); |
||||
|
await pc.setLocalDescription(offer); |
||||
|
ws.send(JSON.stringify({ |
||||
|
'sdp': pc.localDescription.sdp, |
||||
|
'type': pc.localDescription.type |
||||
|
})); |
||||
|
}; |
||||
</script> |
</script> |
||||
</body> |
</body> |
||||
</html> |
</html> |
||||
|
@ -1,122 +0,0 @@ |
|||||
<!DOCTYPE html> |
|
||||
<html lang="en"> |
|
||||
<head> |
|
||||
<meta charset="UTF-8"> |
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
||||
<title>ZFBOX</title> |
|
||||
<link rel="stylesheet" href="{{ url_for('main.static', filename='css/bootstrap.min.css') }}"> |
|
||||
<!-- <link href="../static/resources/css/bootstrap.min.css" rel="stylesheet"> --> |
|
||||
|
|
||||
<style> |
|
||||
.bd-placeholder-img { |
|
||||
font-size: 1.125rem; |
|
||||
text-anchor: middle; |
|
||||
-webkit-user-select: none; |
|
||||
-moz-user-select: none; |
|
||||
user-select: none; |
|
||||
} |
|
||||
|
|
||||
@media (min-width: 768px) { |
|
||||
.bd-placeholder-img-lg { |
|
||||
font-size: 3.5rem; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.b-example-divider { |
|
||||
height: 3rem; |
|
||||
background-color: rgba(0, 0, 0, .1); |
|
||||
border: solid rgba(0, 0, 0, .15); |
|
||||
border-width: 1px 0; |
|
||||
box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15); |
|
||||
} |
|
||||
|
|
||||
.b-example-vr { |
|
||||
flex-shrink: 0; |
|
||||
width: 1.5rem; |
|
||||
height: 100vh; |
|
||||
} |
|
||||
|
|
||||
.bi { |
|
||||
vertical-align: -.125em; |
|
||||
fill: currentColor; |
|
||||
} |
|
||||
|
|
||||
.nav-scroller { |
|
||||
position: relative; |
|
||||
z-index: 2; |
|
||||
height: 2.75rem; |
|
||||
overflow-y: hidden; |
|
||||
} |
|
||||
|
|
||||
.nav-scroller .nav { |
|
||||
display: flex; |
|
||||
flex-wrap: nowrap; |
|
||||
padding-bottom: 1rem; |
|
||||
margin-top: -1px; |
|
||||
overflow-x: auto; |
|
||||
text-align: center; |
|
||||
white-space: nowrap; |
|
||||
-webkit-overflow-scrolling: touch; |
|
||||
} |
|
||||
|
|
||||
.captcha-group { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
gap: 10px; |
|
||||
} |
|
||||
</style> |
|
||||
<!-- Custom styles for this template --> |
|
||||
<link href="{{ url_for('main.static', filename='css/sign-in.css') }}" rel="stylesheet"> |
|
||||
<!-- <link href="../static/resources/css/sign-in.css" rel="stylesheet"> --> |
|
||||
</head> |
|
||||
<body class="text-center"> |
|
||||
<main class="form-signin w-100 m-auto"> |
|
||||
{% with messages = get_flashed_messages(with_categories=true) %} |
|
||||
{% if messages %} |
|
||||
<div class="alert alert-danger" role="alert"> |
|
||||
{% for category, message in messages %} |
|
||||
{{ message }} |
|
||||
{% endfor %} |
|
||||
</div> |
|
||||
{% endif %} |
|
||||
{% endwith %} |
|
||||
<form id="loginForm" method="post" action="/api/user/login"> |
|
||||
<img class="mb-4" src="images/登录/zf.svg" alt="" width="72" height="57"> |
|
||||
<h1 class="h3 mb-3 fw-normal">ZF-AI</h1> |
|
||||
|
|
||||
<div class="form-floating"> |
|
||||
<input type="text" class="form-control" id="username" name="username" placeholder="UserName" required> |
|
||||
<label for="username">UserName</label> |
|
||||
</div> |
|
||||
<div class="form-floating"> |
|
||||
<input type="password" class="form-control" id="password" name="password" placeholder="Password" required> |
|
||||
<label for="password">Password</label> |
|
||||
</div> |
|
||||
|
|
||||
<div class="captcha-group"> |
|
||||
<div class="form-floating captcha"> |
|
||||
<input type="text" class="form-control" id="captcha" name="captcha" placeholder="Captcha" required> |
|
||||
<label for="captcha">Captcha</label> |
|
||||
</div> |
|
||||
<img id="captchaImage" src="" alt="Captcha" class="captcha"> |
|
||||
</div> |
|
||||
|
|
||||
<button class="w-100 btn btn-lg btn-primary" type="submit">登 录</button> |
|
||||
<p class="mt-5 mb-3 text-muted">© 2024–2025 ZFKJ All Rights Reserved</p> |
|
||||
</form> |
|
||||
</main> |
|
||||
<script> |
|
||||
const form = document.getElementById('loginForm'); |
|
||||
const captchaImage = document.getElementById('captchaImage'); |
|
||||
captchaImage.src = '/api/user/code?' + new Date().getTime(); |
|
||||
|
|
||||
form.addEventListener('submit', function(event) { |
|
||||
|
|
||||
}); |
|
||||
|
|
||||
function showError(errorText) { |
|
||||
alert(errorText); |
|
||||
} |
|
||||
</script> |
|
||||
</body> |
|
||||
</html> |
|
@ -1,156 +0,0 @@ |
|||||
<!DOCTYPE html> |
|
||||
<html lang="en"> |
|
||||
<head> |
|
||||
<meta charset="UTF-8"> |
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
||||
<title>布防时间计划</title> |
|
||||
<link rel="stylesheet" href="{{ url_for('main.static', filename='css/bootstrap.min.css') }}"> |
|
||||
<style> |
|
||||
.schedule-table { |
|
||||
width: 100%; |
|
||||
table-layout: fixed; |
|
||||
} |
|
||||
.schedule-table th, .schedule-table td { |
|
||||
text-align: center; |
|
||||
vertical-align: middle; |
|
||||
cursor: pointer; |
|
||||
border: 1px solid #dee2e6; |
|
||||
padding: 8px; |
|
||||
} |
|
||||
.schedule-table td.allowed { |
|
||||
background-color: white; |
|
||||
} |
|
||||
.schedule-table td.blocked { |
|
||||
background-color: blue; |
|
||||
} |
|
||||
</style> |
|
||||
</head> |
|
||||
<body> |
|
||||
<div class="container mt-5"> |
|
||||
<table class="schedule-table table table-bordered"> |
|
||||
<thead> |
|
||||
<tr> |
|
||||
<th>小时</th> |
|
||||
<th>00</th> |
|
||||
<th>01</th> |
|
||||
<th>02</th> |
|
||||
<th>03</th> |
|
||||
<th>04</th> |
|
||||
<th>05</th> |
|
||||
<th>06</th> |
|
||||
<th>07</th> |
|
||||
<th>08</th> |
|
||||
<th>09</th> |
|
||||
<th>10</th> |
|
||||
<th>11</th> |
|
||||
<th>12</th> |
|
||||
<th>13</th> |
|
||||
<th>14</th> |
|
||||
<th>15</th> |
|
||||
<th>16</th> |
|
||||
<th>17</th> |
|
||||
<th>18</th> |
|
||||
<th>19</th> |
|
||||
<th>20</th> |
|
||||
<th>21</th> |
|
||||
<th>22</th> |
|
||||
<th>23</th> |
|
||||
</tr> |
|
||||
</thead> |
|
||||
<tbody> |
|
||||
<!-- Repeat above row for each day of the week (Monday to Saturday) --> |
|
||||
<tr> |
|
||||
<th>星期一</th> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
</tr> |
|
||||
<tr> |
|
||||
<th>星期二</th> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
</tr> |
|
||||
<tr> |
|
||||
<th>星期三</th> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
</tr> |
|
||||
<tr> |
|
||||
<th>星期四</th> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
</tr> |
|
||||
<tr> |
|
||||
<th>星期五</th> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
</tr> |
|
||||
<tr> |
|
||||
<th>星期六</th> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
</tr> |
|
||||
<tr> |
|
||||
<th>星期日</th> |
|
||||
<!-- 24 cells for each hour --> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
<td class="blocked"></td><td class="blocked"></td><td class="blocked"></td><td class="blocked"></td> |
|
||||
</tr> |
|
||||
</tbody> |
|
||||
</table> |
|
||||
<div class="mt-3"> |
|
||||
<div class="d-inline-block mr-3"> |
|
||||
<div class="d-inline-block align-middle" style="width: 20px; height: 20px; background-color: white; border: 1px solid black;"></div> |
|
||||
<span class="align-middle">允许</span> |
|
||||
</div> |
|
||||
<div class="d-inline-block"> |
|
||||
<div class="d-inline-block align-middle" style="width: 20px; height: 20px; background-color: blue;"></div> |
|
||||
<span class="align-middle">阻止</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<script> |
|
||||
document.addEventListener('DOMContentLoaded', () => { |
|
||||
const cells = document.querySelectorAll('.schedule-table tbody td'); |
|
||||
cells.forEach(cell => { |
|
||||
cell.addEventListener('click', () => { |
|
||||
if (cell.classList.contains('blocked')) { |
|
||||
cell.classList.remove('blocked'); |
|
||||
cell.classList.add('allowed'); |
|
||||
} else { |
|
||||
cell.classList.remove('allowed'); |
|
||||
cell.classList.add('blocked'); |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
}); |
|
||||
</script> |
|
||||
</body> |
|
||||
</html> |
|
@ -1,146 +0,0 @@ |
|||||
{% extends 'base.html' %} |
|
||||
|
|
||||
{% block title %}ZFBOX{% endblock %} |
|
||||
|
|
||||
{% block style %} |
|
||||
.nav-scroller { |
|
||||
position: relative; |
|
||||
z-index: 2; |
|
||||
height: 2.75rem; |
|
||||
overflow-y: hidden; |
|
||||
} |
|
||||
|
|
||||
.nav-scroller .nav { |
|
||||
display: flex; |
|
||||
flex-wrap: nowrap; |
|
||||
padding-bottom: 1rem; |
|
||||
margin-top: -1px; |
|
||||
overflow-x: auto; |
|
||||
text-align: center; |
|
||||
white-space: nowrap; |
|
||||
-webkit-overflow-scrolling: touch; |
|
||||
} |
|
||||
|
|
||||
.blue-svg { |
|
||||
color: blue; /* 这将影响所有使用currentColor的fill属性 */ |
|
||||
} |
|
||||
|
|
||||
main { |
|
||||
display: flex; |
|
||||
flex: 1; |
|
||||
overflow: hidden; |
|
||||
} |
|
||||
|
|
||||
.tree-view { |
|
||||
border-right: 1px solid #ddd; |
|
||||
padding: 10px; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
|
|
||||
.video-frame { |
|
||||
position: relative; |
|
||||
width: calc(50% - 10px); /* 默认4画面布局,每行2个视频框架 */ |
|
||||
margin: 5px; |
|
||||
float: left; |
|
||||
background-color: #ccc; /* 默认灰色填充 */ |
|
||||
border: 1px solid #000; /* 边框 */ |
|
||||
} |
|
||||
.video-header { |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
background-color: #2c3e50; |
|
||||
color: #fff; |
|
||||
padding: 5px; |
|
||||
} |
|
||||
.video-area { |
|
||||
width: 100%; |
|
||||
padding-bottom: 75%; /* 4:3 比例 */ |
|
||||
background-color: #000; /* 默认灰色填充 */ |
|
||||
position: relative; |
|
||||
border: 1px solid #ddd; /* 视频区域边框 */ |
|
||||
} |
|
||||
|
|
||||
.video-area img { |
|
||||
display: none; /* 初始隐藏 */ |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
left: 0; |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
object-fit: cover; |
|
||||
} |
|
||||
|
|
||||
.video-buttons { |
|
||||
display: flex; |
|
||||
gap: 10px; |
|
||||
} |
|
||||
|
|
||||
.video-buttons button { |
|
||||
background: none; |
|
||||
border: none; |
|
||||
color: white; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.video-buttons button:hover { |
|
||||
color: #f39c12; |
|
||||
} |
|
||||
|
|
||||
.dropdown-menu { |
|
||||
min-width: 100px; |
|
||||
} |
|
||||
|
|
||||
.video-frame img { |
|
||||
width: 100%; |
|
||||
height: auto; |
|
||||
} |
|
||||
#videoGrid.four .video-frame { |
|
||||
width: calc(50% - 10px); /* 每行4个视频框架 */ |
|
||||
} |
|
||||
#videoGrid.eight .video-frame { |
|
||||
width: calc(12.5% - 10px); /* 每行8个视频框架 */ |
|
||||
} |
|
||||
#videoGrid.nine .video-frame { |
|
||||
width: calc(33.33% - 10px); /* 每行3个视频框架,9画面布局 */ |
|
||||
} |
|
||||
.toggle-buttons { |
|
||||
margin-bottom: 5px; |
|
||||
} |
|
||||
.btn-small { |
|
||||
font-size: 0.75rem; |
|
||||
padding: 0.25rem 0.5rem; |
|
||||
} |
|
||||
.error-message { |
|
||||
position: absolute; |
|
||||
top: 50%; |
|
||||
left: 50%; |
|
||||
transform: translate(-50%, -50%); |
|
||||
color: red; |
|
||||
background-color: rgba(255, 255, 255, 0.8); /* 半透明背景 */ |
|
||||
padding: 10px; |
|
||||
border-radius: 5px; |
|
||||
} |
|
||||
{% endblock %} |
|
||||
|
|
||||
{% block content %} |
|
||||
<div class="container d-flex flex-wrap" > |
|
||||
<div id="treeView" class="tree-view col-md-3 "> |
|
||||
<!-- 动态树视图 --> |
|
||||
</div> |
|
||||
|
|
||||
<div class="video-content col-md-9"> |
|
||||
<div id="videoGrid" class="row four"> |
|
||||
<!-- 动态视频节点 --> |
|
||||
</div> |
|
||||
<div class="toggle-buttons"> |
|
||||
<button id="fourView" class="btn btn-primary btn-small">四画面</button> |
|
||||
<button id="nineView" class="btn btn-secondary btn-small">九画面</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
{% endblock %} |
|
||||
|
|
||||
{% block script %} |
|
||||
<script src="{{ url_for('main.static', filename='scripts/aiortc-client-new.js') }}"></script> |
|
||||
{% endblock %} |
|