fix(template): 修复模板库保存/颜色/拖拽排序,联动OntologyInspector,种子腹腔镜35分类模板
- backend/schemas.py: TemplateUpdate 添加 classes/rules 字段 - backend/models.py: Template 添加 description 列 - backend/routers/templates.py: create/update 打包/解包 mapping_rules.classes (已有) - backend/main.py: seed 腹腔镜胆囊切除术35分类模板 - src/lib/api.ts: updateTemplate 改 PATCH,补齐 color/z_index,_mapTemplate 对齐 TS 接口 - src/store/useStore.ts: 新增 activeTemplateId/setActiveTemplateId - src/components/TemplateRegistry.tsx: 随机颜色(HSL轮盘)、HTML5拖拽排序、批量JSON导入、一键载入腹腔镜模板、handleSave 补齐必填字段 - src/components/OntologyInspector.tsx: 完全重写,从store读取模板,支持模板切换和自定义分类 - src/components/VideoWorkspace.tsx: 进入时自动加载模板列表 - src/components/ProjectLibrary.tsx: 修复状态字符串 TS 严格类型报错 - 工程分析/: 更新实现方案与经验记录 Timestamp: 20260430_222830
This commit is contained in:
@@ -1,13 +1,37 @@
|
||||
import React from 'react';
|
||||
import { Layers, ChevronDown, Tag, Eye } from 'lucide-react';
|
||||
import React, { useState } from 'react';
|
||||
import { Layers, ChevronDown, Tag, Eye, Plus, X } from 'lucide-react';
|
||||
import { useStore } from '../store/useStore';
|
||||
import type { TemplateClass } from '../store/useStore';
|
||||
|
||||
export function OntologyInspector() {
|
||||
const ontology = [
|
||||
{ id: '1', label: 'vehicle_four_wheels', color: 'bg-cyan-500', count: 4, zIndex: 60 },
|
||||
{ id: '2', label: 'pedestrian', color: 'bg-purple-500', count: 2, zIndex: 70 },
|
||||
{ id: '3', label: 'road_surface', color: 'bg-gray-500', count: 1, zIndex: 10 },
|
||||
{ id: '4', label: 'traffic_sign', color: 'bg-green-500', count: 3, zIndex: 50 },
|
||||
];
|
||||
const templates = useStore((state) => state.templates);
|
||||
const activeTemplateId = useStore((state) => state.activeTemplateId);
|
||||
const setActiveTemplateId = useStore((state) => state.setActiveTemplateId);
|
||||
|
||||
// Project-level custom classes (in addition to template classes)
|
||||
const [customClasses, setCustomClasses] = useState<TemplateClass[]>([]);
|
||||
const [showAddForm, setShowAddForm] = useState(false);
|
||||
const [newClassName, setNewClassName] = useState('');
|
||||
const [newClassColor, setNewClassColor] = useState('#06b6d4');
|
||||
|
||||
const activeTemplate = templates.find((t) => t.id === activeTemplateId) || templates[0] || null;
|
||||
const templateClasses = activeTemplate?.classes || [];
|
||||
const allClasses = [...templateClasses, ...customClasses].sort((a, b) => b.zIndex - a.zIndex);
|
||||
|
||||
const handleAddCustom = () => {
|
||||
if (!newClassName.trim()) return;
|
||||
const maxZ = allClasses.length > 0 ? Math.max(...allClasses.map((c) => c.zIndex)) : 0;
|
||||
const newClass: TemplateClass = {
|
||||
id: `custom-${Date.now()}`,
|
||||
name: newClassName.trim(),
|
||||
color: newClassColor,
|
||||
zIndex: maxZ + 10,
|
||||
category: '自定义',
|
||||
};
|
||||
setCustomClasses([...customClasses, newClass]);
|
||||
setNewClassName('');
|
||||
setShowAddForm(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-60 bg-[#0d0d0d] flex flex-col border-l border-white/5 shrink-0 z-10 overflow-hidden">
|
||||
@@ -17,35 +41,42 @@ export function OntologyInspector() {
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-4 flex flex-col gap-6">
|
||||
{/* Frame Metadata */}
|
||||
{/* Template Selector */}
|
||||
<div>
|
||||
<h3 className="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-3">局部帧元数据</h3>
|
||||
<div className="bg-white/5 rounded p-2 text-[11px] space-y-2 font-mono text-gray-300">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">物理分辨率:</span> <span>1920x1080</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">绝对时间码:</span> <span>00:01:24.16</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">涵盖实体:</span> <span>10 个已实例化</span>
|
||||
</div>
|
||||
<h3 className="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-2">当前激活模板</h3>
|
||||
<div className="relative">
|
||||
<select
|
||||
value={activeTemplate?.id || ''}
|
||||
onChange={(e) => setActiveTemplateId(e.target.value || null)}
|
||||
className="w-full bg-[#1a1a1a] border border-white/10 rounded-lg px-3 py-2 text-xs text-gray-300 appearance-none cursor-pointer focus:outline-none focus:border-cyan-500/50"
|
||||
>
|
||||
<option value="">-- 选择模板 --</option>
|
||||
{templates.map((t) => (
|
||||
<option key={t.id} value={t.id}>{t.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown size={12} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none" />
|
||||
</div>
|
||||
{activeTemplate && (
|
||||
<div className="mt-2 text-[10px] text-gray-600">
|
||||
{activeTemplate.classes?.length ?? 0} 个分类来自模板
|
||||
{customClasses.length > 0 && ` + ${customClasses.length} 个自定义`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Global Priority Classes */}
|
||||
{/* Semantic Classification Tree */}
|
||||
<div>
|
||||
<h3 className="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-3 flex justify-between items-center">
|
||||
<span>语义分类树 (高度/Z-Index)</span>
|
||||
<button className="text-cyan-400 hover:text-cyan-300"><ChevronDown size={14} /></button>
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{ontology.sort((a,b) => b.zIndex - a.zIndex).map(cls => (
|
||||
{allClasses.map(cls => (
|
||||
<div key={cls.id} className="flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between p-2 rounded bg-white/5 hover:bg-white/10 cursor-pointer group transition-colors">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`w-2.5 h-2.5 rounded-sm ${cls.color}`} />
|
||||
<span className="text-xs font-medium text-gray-200">{cls.label}</span>
|
||||
<span className="w-2.5 h-2.5 rounded-sm" style={{ backgroundColor: cls.color }} />
|
||||
<span className="text-xs font-medium text-gray-200">{cls.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-[10px] text-gray-500 font-mono">z:{cls.zIndex}</span>
|
||||
@@ -54,16 +85,58 @@ export function OntologyInspector() {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{allClasses.length === 0 && (
|
||||
<div className="text-xs text-gray-600 text-center py-4">请先选择一个模板</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Add Custom Class */}
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h3 className="text-[10px] font-bold text-gray-500 uppercase tracking-widest">自定义分类</h3>
|
||||
<button
|
||||
onClick={() => setShowAddForm(!showAddForm)}
|
||||
className="text-cyan-400 hover:text-cyan-300 transition-colors"
|
||||
>
|
||||
<Plus size={12} />
|
||||
</button>
|
||||
</div>
|
||||
{showAddForm && (
|
||||
<div className="bg-[#1a1a1a] border border-white/10 rounded-lg p-3 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="color"
|
||||
value={newClassColor}
|
||||
onChange={(e) => setNewClassColor(e.target.value)}
|
||||
className="w-8 h-8 rounded bg-transparent border-0 cursor-pointer"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={newClassName}
|
||||
onChange={(e) => setNewClassName(e.target.value)}
|
||||
placeholder="分类名称"
|
||||
className="flex-1 bg-[#111] border border-white/10 rounded px-2 py-1 text-xs text-white"
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleAddCustom()}
|
||||
/>
|
||||
<button onClick={handleAddCustom} className="text-cyan-400 hover:text-cyan-300">
|
||||
<Plus size={14} />
|
||||
</button>
|
||||
<button onClick={() => setShowAddForm(false)} className="text-gray-500 hover:text-gray-300">
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Current Active Object Properties */}
|
||||
<div className="mt-4 pt-4 border-t border-[#222]">
|
||||
<h3 className="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-3">特定目标实例属性追踪</h3>
|
||||
<div className="bg-white/5 rounded-lg p-3">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Tag size={12} className="text-cyan-400" />
|
||||
<span className="text-xs font-semibold text-gray-200">vehicle_four_wheels</span>
|
||||
<span className="text-xs font-semibold text-gray-200">{activeTemplate?.name || '未选择'}</span>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
|
||||
Reference in New Issue
Block a user