预警图片检测触发修复
This commit is contained in:
@@ -130,7 +130,8 @@ async def detect_image(
|
|||||||
"detections": result['detections'],
|
"detections": result['detections'],
|
||||||
"image_base64": img_base64,
|
"image_base64": img_base64,
|
||||||
"stats": result['stats'],
|
"stats": result['stats'],
|
||||||
"alerts": result.get('alerts', []),
|
"alerts": result.get('alerts', []) or result.get('alert_events', []),
|
||||||
|
"alert_events": result.get('alert_events', []),
|
||||||
"behavior_stats": result.get('behavior_stats', {})
|
"behavior_stats": result.get('behavior_stats', {})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,15 +12,6 @@ import torch
|
|||||||
from .loitering_service import get_loitering_service
|
from .loitering_service import get_loitering_service
|
||||||
from .adapters import DetectionAdapter
|
from .adapters import DetectionAdapter
|
||||||
from .event import AlertRuleEngine, EventAggregator, EventDecisionEngine
|
from .event import AlertRuleEngine, EventAggregator, EventDecisionEngine
|
||||||
from .event.frame_accumulator import MultiFrameAccumulator
|
|
||||||
from .event.llm_trigger import LLMTrigger
|
|
||||||
from .llm_analysis_service import (
|
|
||||||
LLMAnalysisService,
|
|
||||||
LLMAnalysisRequest,
|
|
||||||
create_llm_service_from_settings,
|
|
||||||
)
|
|
||||||
from .llm_cost_tracker import LLMCostTracker, get_global_tracker
|
|
||||||
from .result_fusion import ResultFusion
|
|
||||||
from core.settings import get_settings
|
from core.settings import get_settings
|
||||||
from models.event_schemas import DetectionSource
|
from models.event_schemas import DetectionSource
|
||||||
|
|
||||||
@@ -51,49 +42,13 @@ class DetectionService:
|
|||||||
max_active_events=settings.event_engine.max_active_events,
|
max_active_events=settings.event_engine.max_active_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
# LLM 二次判断管道 (MVP-3 / D26-D30)
|
|
||||||
self._llm_enabled = settings.llm.enabled
|
|
||||||
if self._llm_enabled:
|
|
||||||
self._frame_accumulator = MultiFrameAccumulator(
|
|
||||||
window_seconds=settings.llm_trigger.window_seconds,
|
|
||||||
)
|
|
||||||
self._llm_trigger = LLMTrigger(
|
|
||||||
accumulator=self._frame_accumulator,
|
|
||||||
min_consecutive_hits=settings.llm_trigger.min_consecutive_hits,
|
|
||||||
min_avg_confidence=settings.llm_trigger.min_avg_confidence,
|
|
||||||
cooldown_seconds=settings.llm_trigger.cooldown_seconds,
|
|
||||||
severity_bypass=settings.llm_trigger.severity_bypass,
|
|
||||||
)
|
|
||||||
self._llm_service = create_llm_service_from_settings(settings)
|
|
||||||
self._cost_tracker = get_global_tracker()
|
|
||||||
self._result_fusion = ResultFusion(
|
|
||||||
strategy=settings.fusion.strategy,
|
|
||||||
yolo_weight=settings.fusion.yolo_weight,
|
|
||||||
llm_weight=settings.fusion.llm_weight,
|
|
||||||
suppress_on_llm_negative=settings.fusion.suppress_on_llm_negative,
|
|
||||||
fallback_to_yolo=settings.fusion.fallback_to_yolo,
|
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
"LLM 管道已初始化: provider=%s model=%s strategy=%s",
|
|
||||||
settings.llm.provider,
|
|
||||||
settings.llm.model,
|
|
||||||
settings.fusion.strategy,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._frame_accumulator = None
|
|
||||||
self._llm_trigger = None
|
|
||||||
self._llm_service = None
|
|
||||||
self._cost_tracker = None
|
|
||||||
self._result_fusion = None
|
|
||||||
|
|
||||||
async def detect_image(
|
async def detect_image(
|
||||||
self,
|
self,
|
||||||
image: np.ndarray,
|
image: np.ndarray,
|
||||||
model_id: str,
|
model_id: str,
|
||||||
confidence: float = 0.5,
|
confidence: float = 0.5,
|
||||||
iou: float = 0.45,
|
iou: float = 0.45,
|
||||||
algorithm_config: Optional[Dict] = None,
|
algorithm_config: Optional[Dict] = None
|
||||||
region_polygon: Optional[List[List[int]]] = None
|
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
@@ -107,40 +62,6 @@ class DetectionService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 违停检测特殊处理:调用专门的违停检测方法
|
|
||||||
if model_id == 'illegal_parking_detection' and hasattr(model, 'detect_illegal_parking'):
|
|
||||||
# 如果提供了禁停区域,单张图片模式下即时判定(时间阈值设为0)
|
|
||||||
parking_time = 0 if region_polygon else None
|
|
||||||
parking_result = model.detect_illegal_parking(
|
|
||||||
image, conf=confidence, illegal_parking_time=parking_time, region_polygon=region_polygon
|
|
||||||
)
|
|
||||||
detections = []
|
|
||||||
for vehicle in parking_result.get('illegal_parking', []):
|
|
||||||
detections.append({
|
|
||||||
'class': 'illegal_parking',
|
|
||||||
'label': '违停车辆',
|
|
||||||
'confidence': 1.0,
|
|
||||||
'bbox': vehicle['bbox'],
|
|
||||||
'track_id': vehicle.get('track_id'),
|
|
||||||
'parking_duration': vehicle.get('parking_duration', 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
processing_time = time.time() - start_time
|
|
||||||
result_data = {
|
|
||||||
'success': parking_result['success'],
|
|
||||||
'message': parking_result.get('message', '违停检测完成'),
|
|
||||||
'detections': detections,
|
|
||||||
'stats': {
|
|
||||||
**parking_result.get('stats', {}),
|
|
||||||
'total_detections': len(detections),
|
|
||||||
'processing_time': round(processing_time, 3),
|
|
||||||
'model_used': model_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result_data = self._apply_event_pipeline(result_data, model_id)
|
|
||||||
result_data = await self._apply_llm_pipeline(result_data)
|
|
||||||
return result_data
|
|
||||||
|
|
||||||
results = model(image, conf=confidence, iou=iou, verbose=False)
|
results = model(image, conf=confidence, iou=iou, verbose=False)
|
||||||
|
|
||||||
detections = []
|
detections = []
|
||||||
@@ -230,8 +151,6 @@ class DetectionService:
|
|||||||
|
|
||||||
# 事件管道 (MVP-1): 决策 → 规则 → 聚合
|
# 事件管道 (MVP-1): 决策 → 规则 → 聚合
|
||||||
result_data = self._apply_event_pipeline(result_data, model_id)
|
result_data = self._apply_event_pipeline(result_data, model_id)
|
||||||
# LLM 二次判断管道 (MVP-3)
|
|
||||||
result_data = await self._apply_llm_pipeline(result_data, frame=image)
|
|
||||||
|
|
||||||
return result_data
|
return result_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -283,45 +202,6 @@ class DetectionService:
|
|||||||
'stats': None
|
'stats': None
|
||||||
}
|
}
|
||||||
|
|
||||||
# 违停检测特殊处理:调用专门的违停检测方法
|
|
||||||
if model_id == 'illegal_parking_detection' and hasattr(model, 'detect_illegal_parking'):
|
|
||||||
parking_result = model.detect_illegal_parking(
|
|
||||||
frame, conf=confidence
|
|
||||||
)
|
|
||||||
detections = []
|
|
||||||
for vehicle in parking_result.get('illegal_parking', []):
|
|
||||||
detections.append({
|
|
||||||
'class': 'illegal_parking',
|
|
||||||
'label': '违停车辆',
|
|
||||||
'confidence': 1.0,
|
|
||||||
'bbox': vehicle['bbox'],
|
|
||||||
'track_id': vehicle.get('track_id'),
|
|
||||||
'parking_duration': vehicle.get('parking_duration', 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
processing_time = time.time() - start_time
|
|
||||||
fps = 1.0 / processing_time if processing_time > 0 else 0
|
|
||||||
|
|
||||||
result_data = {
|
|
||||||
'success': parking_result['success'],
|
|
||||||
'message': parking_result.get('message', '违停检测完成'),
|
|
||||||
'detections': detections,
|
|
||||||
'stats': {
|
|
||||||
**parking_result.get('stats', {}),
|
|
||||||
'total_detections': len(detections),
|
|
||||||
'fps': round(fps, 2),
|
|
||||||
'processing_time': round(processing_time, 3),
|
|
||||||
'model_used': model_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result_data = self._apply_event_pipeline(result_data, model_id)
|
|
||||||
result_data = await self._apply_llm_pipeline(result_data, frame=frame)
|
|
||||||
|
|
||||||
if draw:
|
|
||||||
frame = self.draw_detections(frame, detections, fps)
|
|
||||||
|
|
||||||
return frame, result_data
|
|
||||||
|
|
||||||
results = model(frame, conf=confidence, iou=iou, verbose=False)
|
results = model(frame, conf=confidence, iou=iou, verbose=False)
|
||||||
|
|
||||||
detections = []
|
detections = []
|
||||||
@@ -436,8 +316,6 @@ class DetectionService:
|
|||||||
|
|
||||||
# 事件管道 (MVP-1): 决策 → 规则 → 聚合
|
# 事件管道 (MVP-1): 决策 → 规则 → 聚合
|
||||||
result_data = self._apply_event_pipeline(result_data, model_id=model_id)
|
result_data = self._apply_event_pipeline(result_data, model_id=model_id)
|
||||||
# LLM 二次判断管道 (MVP-3)
|
|
||||||
result_data = await self._apply_llm_pipeline(result_data, frame=frame)
|
|
||||||
|
|
||||||
return frame, result_data
|
return frame, result_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -552,8 +430,6 @@ class DetectionService:
|
|||||||
model_id='fire_composite',
|
model_id='fire_composite',
|
||||||
source=DetectionSource.COMPOSITE,
|
source=DetectionSource.COMPOSITE,
|
||||||
)
|
)
|
||||||
# LLM 二次判断管道 (MVP-3)
|
|
||||||
result_data = await self._apply_llm_pipeline(result_data, frame=image)
|
|
||||||
return result_data
|
return result_data
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -631,15 +507,13 @@ class DetectionService:
|
|||||||
model_id: Optional[str] = None,
|
model_id: Optional[str] = None,
|
||||||
source_id: Optional[str] = None,
|
source_id: Optional[str] = None,
|
||||||
source: DetectionSource = DetectionSource.YOLO,
|
source: DetectionSource = DetectionSource.YOLO,
|
||||||
frame: Optional[np.ndarray] = None,
|
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
"""对检测结果执行 决策 → 规则 → 聚合 → LLM 二次判断 → 融合 管道。
|
"""对检测结果执行 决策 → 规则 → 聚合 三段管道。
|
||||||
|
|
||||||
在 ``result_data`` 中追加以下字段:
|
在 ``result_data`` 中追加两个字段:
|
||||||
|
|
||||||
- ``candidate_events``: List[dict] 决策引擎产出的候选事件
|
- ``candidate_events``: List[dict] 决策引擎产出的候选事件
|
||||||
- ``alert_events``: List[dict] 规则命中后经聚合的预警事件
|
- ``alert_events``: List[dict] 规则命中后经聚合的预警事件
|
||||||
- ``llm_results``: List[dict] LLM 分析结果 (仅 LLM 启用时)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not result_data.get('success') or not result_data.get('detections'):
|
if not result_data.get('success') or not result_data.get('detections'):
|
||||||
@@ -669,185 +543,6 @@ class DetectionService:
|
|||||||
|
|
||||||
return result_data
|
return result_data
|
||||||
|
|
||||||
async def _apply_llm_pipeline(
|
|
||||||
self,
|
|
||||||
result_data: Dict,
|
|
||||||
frame: Optional[np.ndarray] = None,
|
|
||||||
) -> Dict:
|
|
||||||
"""LLM 二次判断管道 (MVP-3 / D35)。
|
|
||||||
|
|
||||||
在已有 ``alert_events`` 的基础上:
|
|
||||||
1. 将候选事件喂给 LLMTrigger 判断是否需要 LLM 复审
|
|
||||||
2. 对触发的决策调用 LLMAnalysisService
|
|
||||||
3. 用 ResultFusion 融合 YOLO + LLM 结果
|
|
||||||
4. 用 LLMCostTracker 记录成本
|
|
||||||
|
|
||||||
追加字段:
|
|
||||||
- ``llm_results``: List[dict] LLM 分析结果列表
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not self._llm_enabled:
|
|
||||||
result_data['llm_results'] = []
|
|
||||||
return result_data
|
|
||||||
|
|
||||||
alert_events = result_data.get('alert_events', [])
|
|
||||||
if not alert_events:
|
|
||||||
result_data['llm_results'] = []
|
|
||||||
return result_data
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"[LLM管道] 开始评估 | alert_events=%d | 有frame=%s",
|
|
||||||
len(alert_events),
|
|
||||||
frame is not None and frame.size > 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 1. 从 alert_events 重建 CandidateEvent 列表供 LLMTrigger 评估
|
|
||||||
from models.event_schemas import CandidateEvent, AlertEvent, EventType, SeverityLevel, UnifiedDetection
|
|
||||||
candidates_for_llm = []
|
|
||||||
for evt_dict in alert_events:
|
|
||||||
try:
|
|
||||||
# AlertEvent 用 detections(列表), CandidateEvent 用 detection(单个) → 取首个
|
|
||||||
dets = evt_dict.get('detections', [])
|
|
||||||
det = dets[0] if dets else None
|
|
||||||
|
|
||||||
candidate = CandidateEvent(
|
|
||||||
event_type=EventType(evt_dict['event_type']),
|
|
||||||
severity=SeverityLevel(evt_dict.get('severity', 'high')),
|
|
||||||
confidence=evt_dict.get('confidence', 0.5),
|
|
||||||
detection=det,
|
|
||||||
source_id=evt_dict.get('source_id'),
|
|
||||||
timestamp=evt_dict.get('timestamp', evt_dict.get('first_seen')),
|
|
||||||
triggered_rules=[evt_dict.get('rule_name')] if evt_dict.get('rule_name') else [],
|
|
||||||
)
|
|
||||||
candidates_for_llm.append(candidate)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.debug("[LLM管道] 重建候选事件失败: %s | 原始数据: %s", exc, {k: v for k, v in evt_dict.items() if k != 'detections'})
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not candidates_for_llm:
|
|
||||||
logger.info("[LLM管道] 无有效候选事件,跳过")
|
|
||||||
result_data['llm_results'] = []
|
|
||||||
return result_data
|
|
||||||
|
|
||||||
# 2. LLMTrigger 评估
|
|
||||||
trigger_decisions = self._llm_trigger.evaluate(candidates_for_llm)
|
|
||||||
|
|
||||||
if not trigger_decisions:
|
|
||||||
logger.info("[LLM管道] 触发器评估: 未达到触发条件 (需连续%d帧或critical严重性)", self._llm_trigger.min_consecutive_hits)
|
|
||||||
result_data['llm_results'] = []
|
|
||||||
return result_data
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"[LLM管道] 触发器命中 %d 个决策 | 详情: %s",
|
|
||||||
len(trigger_decisions),
|
|
||||||
[{d.candidate.event_type.value: d.reason} for d in trigger_decisions],
|
|
||||||
)
|
|
||||||
|
|
||||||
# 3. 逐个触发决策调用 LLM + 融合
|
|
||||||
llm_results = []
|
|
||||||
fused_alert_events = []
|
|
||||||
for decision in trigger_decisions:
|
|
||||||
llm_result = None
|
|
||||||
|
|
||||||
# 检查成本追踪器是否允许调用
|
|
||||||
if self._cost_tracker and not self._cost_tracker.can_call():
|
|
||||||
logger.info(
|
|
||||||
"[LLM管道] 调用被降级拦截: %s",
|
|
||||||
self._cost_tracker.reason_for_block(),
|
|
||||||
)
|
|
||||||
elif self._llm_service:
|
|
||||||
# 构造 LLM 请求
|
|
||||||
frames = [frame] if frame is not None and frame.size > 0 else []
|
|
||||||
logger.info(
|
|
||||||
"[LLM管道] 调用 LLM 分析 | 事件=%s | 置信度=%.2f | 图片=%d张",
|
|
||||||
decision.candidate.event_type.value,
|
|
||||||
decision.candidate.confidence,
|
|
||||||
len(frames),
|
|
||||||
)
|
|
||||||
request = LLMAnalysisRequest(
|
|
||||||
candidate=decision.candidate,
|
|
||||||
frames=frames,
|
|
||||||
)
|
|
||||||
llm_result = await self._llm_service.analyze_with_fallback(request)
|
|
||||||
|
|
||||||
if llm_result:
|
|
||||||
logger.info(
|
|
||||||
"[LLM管道] LLM 返回 | confirmed=%s | confidence=%.2f | reasoning=%.80s",
|
|
||||||
llm_result.confirmed,
|
|
||||||
llm_result.confidence,
|
|
||||||
(llm_result.reasoning or ''),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning("[LLM管道] LLM 返回空结果 (可能降级到 mock)")
|
|
||||||
|
|
||||||
# 记录成本
|
|
||||||
if self._cost_tracker and llm_result:
|
|
||||||
self._cost_tracker.record(
|
|
||||||
llm_result,
|
|
||||||
image_count=len(frames) or 1,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 4. 找到对应的 AlertEvent 进行融合
|
|
||||||
alert_dict = None
|
|
||||||
for evt_dict in alert_events:
|
|
||||||
if (
|
|
||||||
evt_dict.get('event_type') == decision.candidate.event_type.value
|
|
||||||
and evt_dict.get('source_id') == decision.candidate.source_id
|
|
||||||
):
|
|
||||||
alert_dict = evt_dict
|
|
||||||
break
|
|
||||||
|
|
||||||
if alert_dict is not None and self._result_fusion:
|
|
||||||
try:
|
|
||||||
alert_event = AlertEvent(**alert_dict)
|
|
||||||
outcome = self._result_fusion.fuse(alert_event, llm_result)
|
|
||||||
if outcome.alert is not None and not outcome.suppressed:
|
|
||||||
fused_alert_events.append(outcome.alert.model_dump(mode='json'))
|
|
||||||
llm_results.append({
|
|
||||||
'trigger_reason': decision.reason,
|
|
||||||
'llm_confirmed': llm_result.confirmed if llm_result else None,
|
|
||||||
'llm_confidence': llm_result.confidence if llm_result else None,
|
|
||||||
'llm_reasoning': llm_result.reasoning if llm_result else None,
|
|
||||||
'fusion_strategy': outcome.strategy,
|
|
||||||
'fusion_reason': outcome.reason,
|
|
||||||
'final_confidence': outcome.final_confidence,
|
|
||||||
'suppressed': outcome.suppressed,
|
|
||||||
})
|
|
||||||
logger.info(
|
|
||||||
"[LLM管道] 融合完成 | 策略=%s | 最终置信度=%.2f | 抑制=%s | 原因=%s",
|
|
||||||
outcome.strategy,
|
|
||||||
outcome.final_confidence,
|
|
||||||
outcome.suppressed,
|
|
||||||
outcome.reason,
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error("LLM 融合失败: %s", exc)
|
|
||||||
llm_results.append({
|
|
||||||
'trigger_reason': decision.reason,
|
|
||||||
'llm_confirmed': None,
|
|
||||||
'error': str(exc),
|
|
||||||
})
|
|
||||||
|
|
||||||
# 用融合后的结果替换原始 alert_events
|
|
||||||
if fused_alert_events:
|
|
||||||
result_data['alert_events'] = fused_alert_events
|
|
||||||
|
|
||||||
result_data['llm_results'] = llm_results
|
|
||||||
logger.info(
|
|
||||||
"[LLM管道] 完成 | 总决策=%d | LLM结果=%d | 融合后事件=%d",
|
|
||||||
len(trigger_decisions),
|
|
||||||
len(llm_results),
|
|
||||||
len(fused_alert_events),
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e: # noqa: BLE001
|
|
||||||
logger.error("LLM 管道执行失败: %s", e)
|
|
||||||
result_data['llm_results'] = []
|
|
||||||
result_data['llm_pipeline_error'] = str(e)
|
|
||||||
|
|
||||||
return result_data
|
|
||||||
|
|
||||||
|
|
||||||
def draw_detections(
|
def draw_detections(
|
||||||
self,
|
self,
|
||||||
@@ -884,7 +579,6 @@ class DetectionService:
|
|||||||
'helmet': (255, 255, 0),
|
'helmet': (255, 255, 0),
|
||||||
'no_helmet': (255, 0, 255),
|
'no_helmet': (255, 0, 255),
|
||||||
'cigarette': (0, 165, 255),
|
'cigarette': (0, 165, 255),
|
||||||
'illegal_parking': (0, 0, 255),
|
|
||||||
# 兼容旧模型类别
|
# 兼容旧模型类别
|
||||||
'violence': (0, 0, 255),
|
'violence': (0, 0, 255),
|
||||||
'fight': (0, 0, 255),
|
'fight': (0, 0, 255),
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ DEFAULT_CLASS_TO_EVENT: Dict[str, EventType] = {
|
|||||||
# 火灾
|
# 火灾
|
||||||
"fire": EventType.FIRE,
|
"fire": EventType.FIRE,
|
||||||
"flame": EventType.FIRE,
|
"flame": EventType.FIRE,
|
||||||
|
"火焰": EventType.FIRE,
|
||||||
"smoke": EventType.SMOKE,
|
"smoke": EventType.SMOKE,
|
||||||
|
"烟雾": EventType.SMOKE,
|
||||||
# 抽烟
|
# 抽烟
|
||||||
"smoking": EventType.SMOKING,
|
"smoking": EventType.SMOKING,
|
||||||
"cigarette": EventType.SMOKING,
|
"cigarette": EventType.SMOKING,
|
||||||
|
|||||||
@@ -583,8 +583,13 @@ const handleUploadSuccess = (response) => {
|
|||||||
if (response.data.alerts && response.data.alerts.length > 0) {
|
if (response.data.alerts && response.data.alerts.length > 0) {
|
||||||
alerts.value = response.data.alerts
|
alerts.value = response.data.alerts
|
||||||
response.data.alerts.forEach(alert => {
|
response.data.alerts.forEach(alert => {
|
||||||
|
// 同步到全局预警中心,供 AlertList 展示
|
||||||
|
alertStore.addAlert(alert)
|
||||||
|
// 兼容两种字段名: 旧格式 {type, message} / 新格式 {event_type, metadata.description}
|
||||||
|
const alertType = alert.type || alert.event_type
|
||||||
|
const alertMessage = alert.message || (alert.metadata && alert.metadata.description) || ''
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: `行为告警: ${alert.type} - ${alert.message}`,
|
message: `预警: ${alertType} - ${alertMessage}`,
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
duration: 3000
|
duration: 3000
|
||||||
})
|
})
|
||||||
@@ -1117,8 +1122,6 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.image-container {
|
.image-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 16 / 9;
|
|
||||||
min-height: 400px;
|
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1130,8 +1133,8 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.display-image {
|
.display-image {
|
||||||
width: 100%;
|
max-width: 100%;
|
||||||
height: 100%;
|
max-height: 600px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
background: #000;
|
background: #000;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user