Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
|
1f61f60a93 | 9 months ago |
@ -0,0 +1,102 @@ |
|||
import threading |
|||
import queue |
|||
import datetime |
|||
import cv2 |
|||
from core.DBManager import DBManager |
|||
from myutils.ConfigManager import myCongif |
|||
|
|||
class WarnData: |
|||
def __init__(self): |
|||
self.width = None #视频画面的width |
|||
self.height = None #视频画面的height |
|||
self.channel_id = None |
|||
self.model_name = None #模型名称,如人员入侵 |
|||
self.img_buffer = None #视频缓冲区 赋值时要拷贝一个备份 |
|||
|
|||
self.warn_text = None |
|||
self.channel_name = None |
|||
|
|||
|
|||
|
|||
class WarnManager: |
|||
def __init__(self): |
|||
self.warn_q = queue.Queue() #线程安全 |
|||
self.brun = True |
|||
# 保存视频相关内容 |
|||
self.FPS = myCongif.get_data("verify_rate") # 视频帧率--是否能实现动态帧率 |
|||
self.fourcc = cv2.VideoWriter_fourcc(*'mp4v') # 使用 mp4 编码 |
|||
|
|||
def __del__(self): |
|||
pass |
|||
|
|||
def add_warn_data(self,warn_data): |
|||
self.warn_q.put(warn_data) |
|||
|
|||
def th_warnmanager(self): |
|||
myDBM = DBManager() |
|||
myDBM.connect() |
|||
while self.brun: |
|||
warn_data = self.warn_q.get() |
|||
self.save_warn(warn_data.model_name,warn_data.img_buffer,warn_data.width,warn_data.height, |
|||
warn_data.channel_id,self.FPS,self.fourcc,myDBM) |
|||
self.send_warn() |
|||
del warn_data.img_buffer |
|||
del warn_data |
|||
|
|||
|
|||
def start_warnmanager_th(self): |
|||
th_warn = threading.Thread(target=self.th_warnmanager) # 一个视频通道一个线程,线程句柄暂时部保留 |
|||
th_warn.start() |
|||
|
|||
def stop_warnmanager_th(self): |
|||
self.brun = False |
|||
del self.warn_q |
|||
|
|||
def send_warn(self): |
|||
'''发送报警信息''' |
|||
pass |
|||
|
|||
def save_warn(self,model_name,buffer,width,height,channnel_id,FPS,fourcc,myDBM): |
|||
''' |
|||
保存报警信息 --- 涉及到I/O操作可以通过线程取执行 -- 避免主线程阻塞 --还未验证-2024-7-6 |
|||
:param model_name: 模型名称,如人员入侵 |
|||
:param w_s_count: 报警已存储的最新帧序列 |
|||
:param buffer_count: 当前视频缓冲区的最新帧序列 |
|||
:param buffer: 视频缓存区 |
|||
:param width: 视频画面的width |
|||
:param height: 视频画面的height |
|||
:param channnel_id: 视频通道ID |
|||
:return: ret 数据库操作记录 |
|||
''' |
|||
now = datetime.datetime.now() # 获取当前日期和时间 |
|||
current_time_str = now.strftime("%Y-%m-%d_%H-%M-%S") |
|||
filename = f"{channnel_id}_{current_time_str}" |
|||
save_path = myCongif.get_data("warn_video_path") |
|||
# 保存视频 |
|||
video_writer = cv2.VideoWriter(f"{save_path}{filename}.mp4", fourcc, FPS, (width, height)) |
|||
if not video_writer.isOpened(): |
|||
print(f"Failed to open video writer for model/warn/{filename}.mp4") |
|||
return False |
|||
ilen = len(buffer) |
|||
istart = 0; |
|||
iend = ilen |
|||
|
|||
for i in range(len(buffer)): |
|||
video_writer.write(buffer[i]) |
|||
video_writer.release() |
|||
# 保存图片 |
|||
ret = cv2.imwrite(f"model/warn/{filename}.png", buffer[-1]) |
|||
# buffer使用完后删除 |
|||
del buffer |
|||
if not ret: |
|||
print("保存图片失败") |
|||
return False |
|||
# 保存数据库 |
|||
|
|||
strsql = (f"INSERT INTO warn (model_name ,video_path ,img_path ,creat_time,channel_id ) " |
|||
f"Values ('{model_name}','model/warn/{filename}.mp4','model/warn/{filename}.png'," |
|||
f"'{current_time_str}','{channnel_id}');") |
|||
ret = myDBM.do_sql(strsql) |
|||
del myDBM # 释放数据库连接资源 |
|||
return ret |
|||
|
@ -1,5 +1,5 @@ |
|||
from quart import Blueprint |
|||
|
|||
main = Blueprint('main', __name__,template_folder='templates') |
|||
main = Blueprint('main', __name__,static_folder='static/resources',template_folder='templates') |
|||
|
|||
from . import routes |
|||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 232 KiB |
@ -1,6 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="35px" height="40px" xmlns="http://www.w3.org/2000/svg"> |
|||
<g transform="matrix(1 0 0 1 -1165 -28 )"> |
|||
<path d="M 34.767578125 28.9192708333333 C 34.9225260416667 30.2300347222222 35 31.6232638888889 35 33.0989583333333 C 35 34.9913194444444 34.4303385416667 36.6145833333333 33.291015625 37.96875 C 32.1516927083333 39.3229166666667 30.7799479166667 40 29.17578125 40 L 5.82421875 40 C 4.22005208333333 40 2.84830729166667 39.3229166666667 1.708984375 37.96875 C 0.569661458333333 36.6145833333333 0 34.9913194444444 0 33.0989583333333 C 0 31.6232638888889 0.0774739583333335 30.2300347222222 0.232421875 28.9192708333333 C 0.387369791666667 27.6085069444444 0.674479166666667 26.2890625 1.09375 24.9609375 C 1.51302083333333 23.6328125 2.04622395833333 22.4956597222222 2.693359375 21.5494791666667 C 3.34049479166667 20.6032986111111 4.197265625 19.8307291666667 5.263671875 19.2317708333333 C 6.330078125 18.6328125 7.55598958333333 18.3333333333333 8.94140625 18.3333333333333 C 11.3294270833333 20.5555555555556 14.1822916666667 21.6666666666667 17.5 21.6666666666667 C 20.8177083333333 21.6666666666667 23.6705729166667 20.5555555555556 26.05859375 18.3333333333333 C 27.4440104166667 18.3333333333333 28.669921875 18.6328125 29.736328125 19.2317708333333 C 30.802734375 19.8307291666667 31.6595052083333 20.6032986111111 32.306640625 21.5494791666667 C 32.9537760416667 22.4956597222222 33.4869791666667 23.6328125 33.90625 24.9609375 C 34.3255208333333 26.2890625 34.6126302083333 27.6085069444444 34.767578125 28.9192708333333 Z M 24.923828125 2.9296875 C 26.974609375 4.8828125 28 7.23958333333333 28 10 C 28 12.7604166666667 26.974609375 15.1171875 24.923828125 17.0703125 C 22.873046875 19.0234375 20.3984375 20 17.5 20 C 14.6015625 20 12.126953125 19.0234375 10.076171875 17.0703125 C 8.025390625 15.1171875 7 12.7604166666667 7 10 C 7 7.23958333333333 8.025390625 4.8828125 10.076171875 2.9296875 C 12.126953125 0.9765625 14.6015625 0 17.5 0 C 20.3984375 0 22.873046875 0.9765625 24.923828125 2.9296875 Z " fill-rule="nonzero" fill="#000000" stroke="none" transform="matrix(1 0 0 1 1165 28 )" /> |
|||
</g> |
|||
</svg> |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,15 @@ |
|||
.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; |
|||
} |
@ -0,0 +1,33 @@ |
|||
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; |
|||
} |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,47 @@ |
|||
|
|||
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; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,608 @@ |
|||
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); |
|||
}); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
@ -0,0 +1,46 @@ |
|||
<!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> |
@ -0,0 +1,291 @@ |
|||
{% 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 %} |
After Width: | Height: | Size: 3.8 KiB |
@ -0,0 +1,35 @@ |
|||
<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,45 +1,176 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<html lang="en"> |
|||
<head> |
|||
<title>WebRTC Video Stream</title> |
|||
<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> |
|||
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> |
|||
<body> |
|||
<h1>WebRTC Video Stream</h1> |
|||
<video id="video" autoplay playsinline></video> |
|||
<header> |
|||
<nav class="navbar navbar-expand-lg navbar-light"> |
|||
<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> |
|||
const pc = new RTCPeerConnection(); |
|||
const video = document.getElementById('video'); |
|||
|
|||
pc.ontrack = (event) => { |
|||
video.srcObject = event.streams[0]; |
|||
}; |
|||
|
|||
const ws = new WebSocket('ws://' + window.location.host + '/api/ws'); |
|||
|
|||
ws.onmessage = async (event) => { |
|||
const data = JSON.parse(event.data); |
|||
await pc.setRemoteDescription(new RTCSessionDescription(data)); |
|||
|
|||
if (data.type === 'offer') { |
|||
const answer = await pc.createAnswer(); |
|||
await pc.setLocalDescription(answer); |
|||
ws.send(JSON.stringify({ |
|||
'sdp': pc.localDescription.sdp, |
|||
'type': pc.localDescription.type |
|||
})); |
|||
} |
|||
}; |
|||
|
|||
ws.onopen = async () => { |
|||
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 |
|||
})); |
|||
}; |
|||
document.getElementById('fourView').addEventListener('click', function() { |
|||
const videoGrid = document.getElementById('videoGrid'); |
|||
videoGrid.classList.remove('eight'); |
|||
videoGrid.classList.add('four'); |
|||
videoGrid.innerHTML = ` |
|||
<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> |
|||
`; |
|||
}); |
|||
|
|||
document.getElementById('eightView').addEventListener('click', function() { |
|||
const videoGrid = document.getElementById('videoGrid'); |
|||
videoGrid.classList.remove('four'); |
|||
videoGrid.classList.add('eight'); |
|||
videoGrid.innerHTML = ` |
|||
<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> |
|||
<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> |
|||
`; |
|||
}); |
|||
</script> |
|||
</body> |
|||
</html> |
|||
|
@ -0,0 +1,122 @@ |
|||
<!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> |
@ -0,0 +1,156 @@ |
|||
<!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> |
@ -0,0 +1,146 @@ |
|||
{% 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 %} |
@ -1,170 +0,0 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<title>登录</title> |
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/> |
|||
<meta http-equiv="content-type" content="text/html; charset=utf-8"/> |
|||
<link href="resources/css/axure_rp_page.css" type="text/css" rel="stylesheet"/> |
|||
<link href="data/styles.css" type="text/css" rel="stylesheet"/> |
|||
<link href="files/登录/styles.css" type="text/css" rel="stylesheet"/> |
|||
<script src="resources/scripts/jquery-3.2.1.min.js"></script> |
|||
<script src="resources/scripts/axure/axQuery.js"></script> |
|||
<script src="resources/scripts/axure/globals.js"></script> |
|||
<script src="resources/scripts/axutils.js"></script> |
|||
<script src="resources/scripts/axure/annotation.js"></script> |
|||
<script src="resources/scripts/axure/axQuery.std.js"></script> |
|||
<script src="resources/scripts/axure/doc.js"></script> |
|||
<script src="resources/scripts/messagecenter.js"></script> |
|||
<script src="resources/scripts/axure/events.js"></script> |
|||
<script src="resources/scripts/axure/recording.js"></script> |
|||
<script src="resources/scripts/axure/action.js"></script> |
|||
<script src="resources/scripts/axure/expr.js"></script> |
|||
<script src="resources/scripts/axure/geometry.js"></script> |
|||
<script src="resources/scripts/axure/flyout.js"></script> |
|||
<script src="resources/scripts/axure/model.js"></script> |
|||
<script src="resources/scripts/axure/repeater.js"></script> |
|||
<script src="resources/scripts/axure/sto.js"></script> |
|||
<script src="resources/scripts/axure/utils.temp.js"></script> |
|||
<script src="resources/scripts/axure/variables.js"></script> |
|||
<script src="resources/scripts/axure/drag.js"></script> |
|||
<script src="resources/scripts/axure/move.js"></script> |
|||
<script src="resources/scripts/axure/visibility.js"></script> |
|||
<script src="resources/scripts/axure/style.js"></script> |
|||
<script src="resources/scripts/axure/adaptive.js"></script> |
|||
<script src="resources/scripts/axure/tree.js"></script> |
|||
<script src="resources/scripts/axure/init.temp.js"></script> |
|||
<script src="resources/scripts/axure/legacy.js"></script> |
|||
<script src="resources/scripts/axure/viewer.js"></script> |
|||
<script src="resources/scripts/axure/math.js"></script> |
|||
<script src="resources/scripts/axure/jquery.nicescroll.min.js"></script> |
|||
<script src="data/document.js"></script> |
|||
<script src="files/登录/data.js"></script> |
|||
<script type="text/javascript"> |
|||
$axure.utils.getTransparentGifPath = function() { return 'resources/images/transparent.gif'; }; |
|||
$axure.utils.getOtherPath = function() { return 'resources/Other.html'; }; |
|||
$axure.utils.getReloadPath = function() { return 'resources/reload.html'; }; |
|||
</script> |
|||
</head> |
|||
<body> |
|||
{% if error %} |
|||
<div style="color: red;">{{ error }}</div> |
|||
{% endif %} |
|||
<div id="base" class=""> |
|||
|
|||
<!-- Unnamed (Rectangle) --> |
|||
<div id="u0" class="ax_default flow_shape"> |
|||
<div id="u0_div" class=""></div> |
|||
<div id="u0_text" class="text " style="display:none; visibility: hidden"> |
|||
<p></p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Rectangle) --> |
|||
<div id="u1" class="ax_default flow_shape"> |
|||
<div id="u1_div" class=""></div> |
|||
<div id="u1_text" class="text " style="display:none; visibility: hidden"> |
|||
<p></p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Rectangle) --> |
|||
<div id="u2" class="ax_default heading_1"> |
|||
<div id="u2_div" class=""></div> |
|||
<div id="u2_text" class="text "> |
|||
<p><span>智凡BOX</span></p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Rectangle) --> |
|||
<div id="u3" class="ax_default heading_3"> |
|||
<div id="u3_div" class=""></div> |
|||
<div id="u3_text" class="text "> |
|||
<p><span>@2024 ZFKJ All Rights Reserved </span></p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Shape) --> |
|||
<div id="u4" class="ax_default icon"> |
|||
<img id="u4_img" class="img " src="images/登录/u4.svg"/> |
|||
<div id="u4_text" class="text " style="display:none; visibility: hidden"> |
|||
<p></p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Rectangle) --> |
|||
<div id="u5" class="ax_default heading_2"> |
|||
<div id="u5_div" class=""></div> |
|||
<div id="u5_text" class="text "> |
|||
<p><span>用户名:</span></p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Text Field) --> |
|||
<div id="u6" class="ax_default text_field"> |
|||
<div id="u6_div" class=""></div> |
|||
<input id="u6_input" type="text" value="" class="u6_input"/> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Rectangle) --> |
|||
<div id="u7" class="ax_default heading_2"> |
|||
<div id="u7_div" class=""></div> |
|||
<div id="u7_text" class="text "> |
|||
<p><span>密码:</span></p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Text Field) --> |
|||
<div id="u8" class="ax_default text_field"> |
|||
<div id="u8_div" class=""></div> |
|||
<input id="u8_input" type="text" value="" class="u8_input"/> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Text Field) --> |
|||
<div id="u9" class="ax_default text_field"> |
|||
<div id="u9_div" class=""></div> |
|||
<input id="u9_input" type="text" value="" class="u9_input"/> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Rectangle) --> |
|||
<div id="u10" class="ax_default heading_2"> |
|||
<div id="u10_div" class=""></div> |
|||
<div id="u10_text" class="text "> |
|||
<p><span>验证码:</span></p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Rectangle) --> |
|||
<div id="u11" class="ax_default primary_button"> |
|||
<div id="u11_div" class=""></div> |
|||
<div id="u11_text" class="text "> |
|||
<p><span>登录</span></p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Image) --> |
|||
<div id="u12" class="ax_default image"> |
|||
<img id="u12_img" class="img " src="images/登录/u12.png"/> |
|||
<div id="u12_text" class="text " style="display:none; visibility: hidden"> |
|||
<p></p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Rectangle) --> |
|||
<div id="u13" class="ax_default button"> |
|||
<div id="u13_div" class=""></div> |
|||
<div id="u13_text" class="text "> |
|||
<p><span>忘记密码</span></p> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Unnamed (Rectangle) --> |
|||
<div id="u14" class="ax_default label"> |
|||
<div id="u14_div" class=""></div> |
|||
<div id="u14_text" class="text "> |
|||
<p><span>注:通过手机验证码修改密码。</span></p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<script src="resources/scripts/axure/ios.js"></script> |
|||
</body> |
|||
</html> |