fix: wizard step 2 — separate assigned/added cameras, show loading state for features
This commit is contained in:
parent
3f3a2a0987
commit
b50ccda82d
@ -52,10 +52,15 @@
|
||||
|
||||
<div id="wizard-step-2" class="wizard-panel" style="display:none">
|
||||
<h3 style="margin-top:0">配置 <span id="step2-device-name">-</span> 的检测任务</h3>
|
||||
<div id="source-list" style="display:flex;flex-direction:column;gap:12px">
|
||||
|
||||
<div style="font-size:12px;font-weight:600;margin-bottom:8px">已配置的摄像头</div>
|
||||
<div id="assigned-sources" style="display:flex;flex-direction:column;gap:8px;margin-bottom:16px">
|
||||
<div class="empty-state compact" id="no-assigned-sources">
|
||||
<div class="muted">该设备尚未配置摄像头。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:16px;padding:12px;border:1px dashed var(--border);border-radius:var(--radius)" id="add-source-panel">
|
||||
<div style="padding:12px;border:1px dashed var(--border);border-radius:var(--radius);margin-bottom:16px">
|
||||
<div style="font-size:12px;font-weight:600;margin-bottom:8px">+ 添加摄像头</div>
|
||||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||||
<input type="text" id="new-source-name" placeholder="摄像头名称(如:车间东门)" style="width:160px;font-size:12px">
|
||||
@ -64,10 +69,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:16px;padding:12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface-soft)" id="feature-selector">
|
||||
<div style="padding:12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface-soft)">
|
||||
<div style="font-size:12px;font-weight:600;margin-bottom:8px">检测功能</div>
|
||||
<div id="feature-list" style="display:flex;gap:12px;flex-wrap:wrap">
|
||||
<span class="muted small">请先选择设备...</span>
|
||||
<span class="muted small" id="feature-placeholder">加载设备能力中...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -125,22 +130,19 @@
|
||||
.feature-check label{font-size:12px;cursor:pointer}
|
||||
</style>
|
||||
|
||||
<script src="/ui/assets/vendor/hls.min.js"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var selectedDevice = null;
|
||||
var selectedDeviceName = "";
|
||||
var step1Sources = [];
|
||||
var step1AllVideoSources = [];
|
||||
var step1Features = null;
|
||||
|
||||
var step2Sources = [];
|
||||
var allVideoSources = [];
|
||||
var step2Assigned = [];
|
||||
var step2Added = [];
|
||||
var step2Features = [];
|
||||
|
||||
function loadAllVideoSources() {
|
||||
return {{.WizardVideoSourcesJSON}} || [];
|
||||
}
|
||||
step1AllVideoSources = loadAllVideoSources();
|
||||
allVideoSources = loadAllVideoSources();
|
||||
|
||||
var currentStep = 1;
|
||||
|
||||
@ -175,88 +177,112 @@
|
||||
if (!selectedDevice) return;
|
||||
document.getElementById("step2-device-name").textContent = selectedDeviceName;
|
||||
setStep(2);
|
||||
|
||||
document.getElementById("feature-placeholder").textContent = "加载设备能力中...";
|
||||
document.getElementById("feature-placeholder").style.display = "";
|
||||
step2Assigned = [];
|
||||
step2Added = [];
|
||||
step2Features = [];
|
||||
renderAssignedSources();
|
||||
renderFeatureList();
|
||||
|
||||
fetch("/ui/wizard/device-info?device_id=" + encodeURIComponent(selectedDevice))
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(info){
|
||||
step1Features = (info.capabilities || []).filter(function(c){return c.available});
|
||||
|
||||
var existingSources = info.sources || [];
|
||||
step2Sources = [];
|
||||
var sourceNames = [];
|
||||
|
||||
existingSources.forEach(function(sn){
|
||||
var src = step1AllVideoSources.find(function(v){return v.name === sn});
|
||||
if (src) {
|
||||
step2Sources.push({name:src.name, url:src.url, existing:true});
|
||||
sourceNames.push(src.name);
|
||||
}
|
||||
});
|
||||
|
||||
// Also add unassigned sources
|
||||
step1AllVideoSources.forEach(function(src){
|
||||
if (sourceNames.indexOf(src.name) < 0) {
|
||||
step2Sources.push({name:src.name, url:src.url, existing:false});
|
||||
sourceNames.push(src.name);
|
||||
var existingNames = (info.sources || []).slice();
|
||||
existingNames.forEach(function(sn){
|
||||
var found = allVideoSources.find(function(v){return v.name === sn});
|
||||
if (found) {
|
||||
step2Assigned.push({name: found.name, url: found.url});
|
||||
}
|
||||
});
|
||||
renderAssignedSources();
|
||||
|
||||
step2Features = [];
|
||||
renderStep2();
|
||||
var caps = (info.capabilities || []).filter(function(c){return c.available});
|
||||
document.getElementById("feature-placeholder").style.display = "none";
|
||||
renderFeatures(caps);
|
||||
})
|
||||
.catch(function(){
|
||||
document.getElementById("feature-placeholder").textContent = "无法获取设备信息";
|
||||
});
|
||||
});
|
||||
|
||||
function renderStep2() {
|
||||
var list = document.getElementById("source-list");
|
||||
list.innerHTML = "";
|
||||
step2Sources.forEach(function(src, i){
|
||||
function renderAssignedSources() {
|
||||
var container = document.getElementById("assigned-sources");
|
||||
var emptyEl = document.getElementById("no-assigned-sources");
|
||||
var allSources = step2Assigned.concat(step2Added);
|
||||
|
||||
var existingCards = container.querySelectorAll(".assigned-source-card");
|
||||
existingCards.forEach(function(c){c.remove()});
|
||||
|
||||
allSources.forEach(function(src){
|
||||
var div = document.createElement("div");
|
||||
div.className = "source-row";
|
||||
div.className = "assigned-source-card source-row";
|
||||
div.innerHTML =
|
||||
'<span class="source-row-name">'+src.name+'</span>'+
|
||||
'<span class="source-row-url mono">'+src.url+'</span>'+
|
||||
'<label style="font-size:11px;display:flex;align-items:center;gap:4px;cursor:pointer">'+
|
||||
'<input type="checkbox" class="source-check" data-index="'+i+'" '+(src.existing ? 'checked' : '')+' style="width:14px;height:14px">'+
|
||||
'启用</label>'+
|
||||
(!src.existing ? '<button class="btn ghost icon-only" style="min-height:22px;width:22px;height:22px;font-size:11px;color:var(--red)" onclick="removeSource('+i+')">×</button>' : '');
|
||||
list.appendChild(div);
|
||||
'<button class="btn ghost icon-only" style="min-height:22px;width:22px;height:22px;font-size:12px;color:var(--red);flex-shrink:0" data-remove="'+src.name+'">×</button>';
|
||||
container.appendChild(div);
|
||||
});
|
||||
renderFeatureList();
|
||||
|
||||
if (allSources.length > 0) {
|
||||
if (emptyEl) emptyEl.style.display = "none";
|
||||
} else {
|
||||
if (emptyEl) emptyEl.style.display = "";
|
||||
}
|
||||
}
|
||||
|
||||
function renderFeatureList() {
|
||||
var features = step1Features || [];
|
||||
document.getElementById("assigned-sources").addEventListener("click", function(e){
|
||||
var btn = e.target.closest("[data-remove]");
|
||||
if (!btn) return;
|
||||
var name = btn.getAttribute("data-remove");
|
||||
step2Assigned = step2Assigned.filter(function(s){return s.name !== name});
|
||||
step2Added = step2Added.filter(function(s){return s.name !== name});
|
||||
renderAssignedSources();
|
||||
});
|
||||
|
||||
function renderFeatures(caps) {
|
||||
var featDiv = document.getElementById("feature-list");
|
||||
featDiv.innerHTML = "";
|
||||
if (features.length === 0) {
|
||||
if (!caps || caps.length === 0) {
|
||||
featDiv.innerHTML = '<span class="muted small">该设备无可用的检测功能。</span>';
|
||||
return;
|
||||
}
|
||||
features.forEach(function(f){
|
||||
caps.forEach(function(f){
|
||||
var enabled = step2Features.indexOf(f.key) >= 0;
|
||||
var label = document.createElement("label");
|
||||
label.className = "feature-check" + (step2Features.indexOf(f.key) >= 0 ? " enabled" : "");
|
||||
label.className = "feature-check" + (enabled ? " enabled" : "");
|
||||
label.innerHTML =
|
||||
'<input type="checkbox" value="'+f.key+'" '+(step2Features.indexOf(f.key) >= 0 ? 'checked' : '')+' style="width:14px;height:14px">'+
|
||||
'<input type="checkbox" value="'+f.key+'" '+(enabled?'checked':'')+' style="width:14px;height:14px">'+
|
||||
'<span>'+f.label+'</span>';
|
||||
label.querySelector("input").addEventListener("change", function(){
|
||||
var checked = this.checked;
|
||||
var key = this.value;
|
||||
if (checked) {
|
||||
if (this.checked) {
|
||||
if (step2Features.indexOf(key) < 0) step2Features.push(key);
|
||||
} else {
|
||||
step2Features = step2Features.filter(function(k){return k !== key});
|
||||
}
|
||||
renderFeatureList();
|
||||
renderFeatures(caps);
|
||||
});
|
||||
featDiv.appendChild(label);
|
||||
});
|
||||
}
|
||||
|
||||
function renderFeatureList() {
|
||||
var featDiv = document.getElementById("feature-list");
|
||||
featDiv.innerHTML = '<span class="muted small" id="feature-placeholder">加载设备能力中...</span>';
|
||||
}
|
||||
|
||||
document.getElementById("btn-add-source").addEventListener("click", function(){
|
||||
var nameEl = document.getElementById("new-source-name");
|
||||
var urlEl = document.getElementById("new-source-url");
|
||||
var name = nameEl.value.trim();
|
||||
var url = urlEl.value.trim();
|
||||
if (!name || !url) return;
|
||||
if (step2Assigned.find(function(s){return s.name===name}) || step2Added.find(function(s){return s.name===name})) {
|
||||
return;
|
||||
}
|
||||
var btn = this;
|
||||
btn.disabled = true;
|
||||
btn.textContent = "添加中...";
|
||||
@ -266,11 +292,11 @@
|
||||
fetch("/ui/wizard/create-source", {method:"POST",body:formData,headers:{"Content-Type":"application/x-www-form-urlencoded"}})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(data){
|
||||
step2Sources.push({name:name, url:url, existing:false});
|
||||
step1AllVideoSources.push({name:name, url:url});
|
||||
step2Added.push({name: name, url: url});
|
||||
allVideoSources.push({name: name, url: url});
|
||||
nameEl.value = "";
|
||||
urlEl.value = "";
|
||||
renderStep2();
|
||||
renderAssignedSources();
|
||||
btn.disabled = false;
|
||||
btn.textContent = "添加";
|
||||
})
|
||||
@ -280,39 +306,28 @@
|
||||
});
|
||||
});
|
||||
|
||||
window.removeSource = function(i) {
|
||||
step2Sources.splice(i, 1);
|
||||
renderStep2();
|
||||
};
|
||||
|
||||
document.getElementById("btn-prev-step").addEventListener("click", function(){setStep(1)});
|
||||
document.getElementById("btn-prev-step3").addEventListener("click", function(){setStep(2)});
|
||||
|
||||
document.getElementById("btn-goto-step3").addEventListener("click", function(){
|
||||
setStep(3);
|
||||
var sources = [];
|
||||
document.querySelectorAll(".source-check:checked").forEach(function(cb){
|
||||
var idx = parseInt(cb.getAttribute("data-index"));
|
||||
sources.push(step2Sources[idx].name);
|
||||
});
|
||||
var allSources = step2Assigned.concat(step2Added);
|
||||
var sourceNames = allSources.map(function(s){return s.name});
|
||||
document.getElementById("summary-device").textContent = selectedDeviceName;
|
||||
document.getElementById("summary-sources").textContent = sources.length > 0 ? sources.join("、") : "未选择";
|
||||
document.getElementById("summary-sources").textContent = sourceNames.length > 0 ? sourceNames.join("、") : "未选择";
|
||||
document.getElementById("summary-features").textContent = step2Features.length > 0 ? step2Features.map(featuresToLabel).join("、") : "未选择";
|
||||
});
|
||||
|
||||
document.getElementById("btn-deploy").addEventListener("click", function(){
|
||||
var sources = [];
|
||||
document.querySelectorAll(".source-check:checked").forEach(function(cb){
|
||||
var idx = parseInt(cb.getAttribute("data-index"));
|
||||
sources.push(step2Sources[idx].name);
|
||||
});
|
||||
var allSources = step2Assigned.concat(step2Added);
|
||||
var sourceNames = allSources.map(function(s){return s.name});
|
||||
var btn = this;
|
||||
btn.disabled = true;
|
||||
btn.textContent = "下发中...";
|
||||
fetch("/ui/wizard/apply", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type":"application/json"},
|
||||
body: JSON.stringify({device_id:selectedDevice, features:step2Features, source_names:sources})
|
||||
body: JSON.stringify({device_id:selectedDevice, features:step2Features, source_names:sourceNames})
|
||||
})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(result){
|
||||
|
||||
Loading…
Reference in New Issue
Block a user