| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817 |
- <template>
- <!-- 设备画面组件:右侧面板上方,包含操作按钮和 2×4 监控画面网格 -->
- <div class="device-view">
- <div class="device-view-content">
- <!-- 顶部操作按钮:最新热源 / 最新闯入 / 最新烟雾 / 关闭声光报警 -->
- <div class="view-actions">
- <button
- v-for="btn in actions"
- :key="btn.key"
- type="button"
- :class="['action-btn', { active: activeAction === btn.key }]"
- @click="onAction(btn.key)"
- >
- {{ btn.label }}
- </button>
- </div>
- <!-- 2×4 监控画面网格 -->
- <div class="view-grid">
- <div class="view-cell">
- <div class="media-wrap">
- <div v-if="renderPlayer" ref="playerRef" style="display: flex; width: 100%; height: 100%; overflow: hidden; pointer-events: none"></div>
- </div>
- </div>
- </div>
- <div class="view-pagination">
- <a-pagination
- size="small"
- :current="pagination.current"
- :page-size="pagination.pageSize"
- :total="total"
- :show-size-changer="false"
- :show-less-items="true"
- :hide-on-single-page="false"
- @change="onPageChange"
- />
- </div>
- </div>
- <!-- 最新热源 / 最新闯入 报警图片弹窗 -->
- <Teleport to="body">
- <div v-if="latestAlarmVisible" class="latest-alarm-mask" @click.self="closeLatestAlarmPopup">
- <div :class="['latest-alarm-panel', { expanded: latestAlarmExpanded }]">
- <div class="latest-alarm-frame">
- <div class="latest-alarm-title-tab">{{ latestAlarmTitle }}</div>
- <div class="latest-alarm-toolbar">
- <button type="button" class="latest-alarm-icon-btn" title="放大" @click="toggleLatestAlarmExpand">
- <svg viewBox="0 0 16 16" width="14" height="14">
- <rect x="3" y="3" width="7" height="7" fill="none" stroke="currentColor" stroke-width="1.3" />
- <path
- d="M9 3 H13 V7 M13 3 L9.5 6.5 M7 9 L3 13 M3 9 V13 H7"
- fill="none"
- stroke="currentColor"
- stroke-width="1.3"
- stroke-linecap="round"
- stroke-linejoin="round"
- />
- </svg>
- </button>
- <button type="button" class="latest-alarm-icon-btn" title="关闭" @click="closeLatestAlarmPopup">
- <svg viewBox="0 0 16 16" width="14" height="14">
- <rect x="2.5" y="2.5" width="8" height="8" fill="none" stroke="currentColor" stroke-width="1.3" />
- <path
- d="M8 8 L13.5 13.5 M10.5 13.5 H13.5 V10.5"
- fill="none"
- stroke="currentColor"
- stroke-width="1.3"
- stroke-linecap="round"
- stroke-linejoin="round"
- />
- </svg>
- </button>
- </div>
- <div class="latest-alarm-image-wrap">
- <img v-if="latestAlarmData.imageUrl" class="latest-alarm-image" :src="latestAlarmData.imageUrl" :alt="latestAlarmTitle" />
- <div v-else class="latest-alarm-image-empty">暂无告警图像</div>
- <div class="latest-alarm-overlay bottom-right">{{ latestAlarmData.deviceName || '--' }}</div>
- </div>
- <div class="latest-alarm-footer">
- <span>告警时间:{{ latestAlarmData.alarmTime || '--' }}</span>
- <span class="latest-alarm-sep">|</span>
- <span>告警设备:{{ latestAlarmData.deviceName || '--' }}</span>
- <span class="latest-alarm-sep">|</span>
- <span>告警类型:{{ latestAlarmData.alarmType || '--' }}</span>
- </div>
- </div>
- </div>
- </div>
- </Teleport>
- </div>
- </template>
- <script setup lang="ts">
- import { nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
- import dayjs from 'dayjs';
- import { useCamera } from '/@/hooks/system/useCameraPianation';
- import { useMessage } from '/@/hooks/web/useMessage';
- import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
- import { dscAlarmLogList } from '../hsqHome.api';
- /** 操作按钮数据结构 */
- interface ActionItem {
- key: string;
- label: string;
- }
- /** 最新报警弹窗展示数据 */
- interface LatestAlarmPopupData {
- imageUrl: string;
- alarmTime: string;
- deviceName: string;
- alarmType: string;
- }
- /** 事件类型编码:热源 / 闯入 */
- const EVENT_TYPE_MAP: Record<string, { code: string; title: string; typeLabel: string }> = {
- heat: { code: '10001', title: '最新热源告警图', typeLabel: '热源报警' },
- intrusion: { code: '10002', title: '最新闯入告警图', typeLabel: '闯入报警' },
- };
- let props = defineProps({
- deviceId: {
- type: String,
- default: '',
- },
- });
- /** 组件自定义事件 */
- const emit = defineEmits<{
- (e: 'action', key: string): void;
- (e: 'select', channel: any): void;
- }>();
- const { getCamera, removeCamera, cameraList, openRegionPreview, Total: total } = useCamera();
- const playerRef = ref();
- const renderPlayer = ref(true);
- /** 网格分页:2×4,每页 8 路画面;autoPlayStart/Count 控制自动播放区间;onCameraSelect 选中回调 */
- const pagination = reactive<{
- current: number;
- pageSize: number;
- autoPlayStart?: number;
- autoPlayCount?: number;
- onCameraSelect?: (camera: any, index: number) => void;
- }>({
- current: 1,
- pageSize: 8,
- onCameraSelect: handleCameraSelect,
- });
- /** 顶部操作按钮列表 */
- const actions: ActionItem[] = [
- { key: 'heat', label: '最新热源' },
- { key: 'intrusion', label: '最新闯入' },
- { key: 'smoke', label: '最新烟雾' },
- { key: 'alarmOff', label: '关闭声光报警' },
- ];
- /** 当前激活的操作按钮 */
- const activeAction = ref('');
- /** 当前选中的通道 ID / 名称 */
- const selectedId = ref('');
- /** 当前选中的画面下标 */
- const selectedIndex = ref(-1);
- /** 取流播放模式:visible 前4播后4停;infrared 前4停后4播 */
- const streamPlayMode = ref<'visible' | 'infrared' | null>(null);
- /** 各画面本地焦距缩放比例(不调接口) */
- const viewZoomMap = reactive<Record<number, number>>({});
- /** 各画面本地焦点模糊程度(px,不调接口) */
- const viewFocusBlurMap = reactive<Record<number, number>>({});
- /** 各画面本地光圈亮度系数(1 为正常,不调接口) */
- const viewIrisBrightMap = reactive<Record<number, number>>({});
- const ZOOM_STEP = 0.15;
- const ZOOM_MIN = 1;
- const ZOOM_MAX = 3;
- const FOCUS_BLUR_STEP = 0.4;
- const FOCUS_BLUR_MIN = 0;
- const FOCUS_BLUR_MAX = 4;
- const IRIS_BRIGHT_STEP = 0.12;
- const IRIS_BRIGHT_MIN = 0.4;
- const IRIS_BRIGHT_MAX = 2;
- const { createMessage } = useMessage();
- /** 最新报警弹窗 */
- const latestAlarmVisible = ref(false);
- const latestAlarmExpanded = ref(false);
- const latestAlarmTitle = ref('最新热源告警图');
- const latestAlarmLoading = ref(false);
- const latestAlarmData = reactive<LatestAlarmPopupData>({
- imageUrl: '',
- alarmTime: '',
- deviceName: '',
- alarmType: '',
- });
- /** 解析报警记录中的图片地址 */
- function resolveAlarmImageUrl(record: Record<string, any>) {
- const raw = record.picName || record.picName2 || '';
- if (!raw) return '';
- const text = String(raw);
- if (/^https?:\/\//i.test(text) || text.startsWith('data:')) return text;
- // 兼容路径中带盘符前缀的情况(与报警详情一致)
- const path = text.includes('h') ? text.substring(text.indexOf('h')) : text;
- return getFileAccessHttpUrl(path.replace(/\\/g, '/'));
- }
- /** 从列表中过滤出指定类型的最新一条记录 */
- function pickLatestAlarmRecord(records: any[], eventCode: string) {
- const matched = (records || []).filter((item) => String(item?.eventType) === eventCode);
- if (!matched.length) return null;
- return matched.slice().sort((a, b) => dayjs(b.createTime || b.alarmTime || 0).valueOf() - dayjs(a.createTime || a.alarmTime || 0).valueOf())[0];
- }
- /** 点击最新热源 / 最新闯入:请求列表后展示对应最新报警图 */
- async function openLatestAlarmByType(key: 'heat' | 'intrusion') {
- if (latestAlarmLoading.value) return;
- const meta = EVENT_TYPE_MAP[key];
- if (!meta) return;
- latestAlarmLoading.value = true;
- try {
- const res = await dscAlarmLogList({ pageNo: 1, pageSize: 1000 });
- const records = res?.records || [];
- console.log('报警图片列表', records);
- const latest = pickLatestAlarmRecord(records, meta.code);
- if (!latest) {
- createMessage.warning(`暂无${meta.typeLabel}数据`);
- return;
- }
- latestAlarmTitle.value = meta.title;
- latestAlarmData.imageUrl = resolveAlarmImageUrl(latest);
- latestAlarmData.alarmTime = String(latest.createTime || latest.alarmTime || '');
- latestAlarmData.deviceName = String(latest.deviceName || latest.devId || latest.strname || '');
- latestAlarmData.alarmType = meta.typeLabel;
- latestAlarmExpanded.value = false;
- latestAlarmVisible.value = true;
- } catch (e) {
- console.error('获取最新报警记录失败', e);
- createMessage.error('获取最新报警记录失败');
- } finally {
- latestAlarmLoading.value = false;
- }
- }
- function toggleLatestAlarmExpand() {
- latestAlarmExpanded.value = !latestAlarmExpanded.value;
- }
- function closeLatestAlarmPopup() {
- latestAlarmVisible.value = false;
- latestAlarmExpanded.value = false;
- }
- /** 点击操作按钮,触发 action 事件 */
- function onAction(key: string) {
- activeAction.value = key;
- if (key === 'heat' || key === 'intrusion') {
- openLatestAlarmByType(key);
- }
- emit('action', key);
- }
- /** 选中监控通道,触发 select 事件 */
- function handleCameraSelect(camera: any, index: number) {
- selectedIndex.value = index;
- selectedId.value = camera?.id || camera?.name || String(index);
- const deviceid = camera?.deviceid || camera?.deviceId || camera?.deviceID || camera?.devId || '';
- emit('select', {
- ...camera,
- index,
- deviceid: deviceid != null && deviceid !== '' ? String(deviceid) : '',
- ip: camera?.ip || camera?.strip || '',
- });
- applyViewLensStyles();
- }
- /** 对当前选中画面做本地焦距缩放(不调接口) */
- function zoomSelectedView(direction: 1 | -1) {
- if (selectedIndex.value < 0) return;
- const index = selectedIndex.value;
- const current = viewZoomMap[index] ?? 1;
- const next = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, Number((current + direction * ZOOM_STEP).toFixed(2))));
- viewZoomMap[index] = next;
- applyViewLensStyles();
- }
- /**
- * 对当前选中画面做本地焦点调节(不调接口)
- * direction > 0 焦点后调(更模糊);direction < 0 焦点前调(更清晰)
- */
- function focusSelectedView(direction: 1 | -1) {
- if (selectedIndex.value < 0) return;
- const index = selectedIndex.value;
- const current = viewFocusBlurMap[index] ?? 0;
- const next = Math.min(FOCUS_BLUR_MAX, Math.max(FOCUS_BLUR_MIN, Number((current + direction * FOCUS_BLUR_STEP).toFixed(2))));
- viewFocusBlurMap[index] = next;
- applyViewLensStyles();
- }
- /**
- * 对当前选中画面做本地光圈调节(不调接口)
- * direction > 0 光圈扩大(更亮);direction < 0 光圈缩小(更暗)
- */
- function irisSelectedView(direction: 1 | -1) {
- if (selectedIndex.value < 0) return;
- const index = selectedIndex.value;
- const current = viewIrisBrightMap[index] ?? 1;
- const next = Math.min(IRIS_BRIGHT_MAX, Math.max(IRIS_BRIGHT_MIN, Number((current + direction * IRIS_BRIGHT_STEP).toFixed(2))));
- viewIrisBrightMap[index] = next;
- applyViewLensStyles();
- }
- /** 获取画面实际绘制节点(video/canvas),避免操作外层容器 */
- function getPaintNode(item: HTMLElement) {
- const media = (item.querySelector('video, canvas') as HTMLElement | null) || (item.querySelector('.liveVideo') as HTMLElement | null);
- if (!media || media === item) return null;
- return media.matches('video, canvas') ? media : (media.querySelector('video, canvas') as HTMLElement | null) || media;
- }
- /** 把焦距缩放 + 焦点模糊 + 光圈亮度应用到对应画面内容(不改变外层容器尺寸) */
- function applyViewLensStyles() {
- const root = playerRef.value as HTMLElement | undefined;
- if (!root) return;
- const items = Array.from(root.querySelectorAll('.live-video-item')) as HTMLElement[];
- items.forEach((item, index) => {
- item.style.overflow = 'hidden';
- item.style.transform = '';
- const paintNode = getPaintNode(item);
- if (!paintNode) return;
- const scale = viewZoomMap[index] ?? 1;
- const blur = viewFocusBlurMap[index] ?? 0;
- const bright = viewIrisBrightMap[index] ?? 1;
- const filters: string[] = [];
- if (bright !== 1) filters.push(`brightness(${bright})`);
- if (blur > 0) filters.push(`blur(${blur}px)`);
- paintNode.style.transformOrigin = 'center center';
- paintNode.style.transition = 'transform 0.15s ease, filter 0.15s ease';
- paintNode.style.transform = scale === 1 ? 'none' : `scale(${scale})`;
- paintNode.style.filter = filters.length ? filters.join(' ') : 'none';
- });
- }
- /** 同步分页上的自动播放区间 */
- function syncPaginationPlayRange() {
- if (streamPlayMode.value === 'visible') {
- pagination.autoPlayStart = 0;
- pagination.autoPlayCount = 4;
- } else if (streamPlayMode.value === 'infrared') {
- pagination.autoPlayStart = 4;
- pagination.autoPlayCount = 4;
- } else {
- pagination.autoPlayStart = undefined;
- pagination.autoPlayCount = undefined;
- }
- }
- /** 按当前取流模式校正播放/停止状态 */
- function applyStreamPlayState() {
- if (!streamPlayMode.value) return;
- const root = playerRef.value as HTMLElement | undefined;
- if (!root) return;
- const videos = Array.from(root.querySelectorAll('video')) as HTMLVideoElement[];
- videos.forEach((video, index) => {
- const shouldPlay = streamPlayMode.value === 'visible' ? index < 4 : index >= 4;
- if (shouldPlay) {
- video.play?.().catch(() => {});
- } else {
- video.pause?.();
- }
- });
- }
- /** 手动勾选可见光取流:前4播放,后4停止 */
- async function enableVisibleLightPlayMode() {
- streamPlayMode.value = 'visible';
- syncPaginationPlayRange();
- await nextTick();
- applyStreamPlayState();
- }
- /** 手动勾选红外取流:前4停止,后4播放 */
- async function enableInfraredPlayMode() {
- streamPlayMode.value = 'infrared';
- syncPaginationPlayRange();
- await nextTick();
- applyStreamPlayState();
- }
- /**
- * 勾选设备 ID 预检:能查到摄像头才用它过滤
- * 后端 devIdList 过滤查不到数据时退回不带过滤,保证画面能展示
- */
- async function resolveUsableDevIdList(raw: string) {
- const ids = String(raw ?? '')
- .split(',')
- .map((id) => id.trim())
- .filter((id) => id !== '' && id !== 'undefined' && id !== 'null')
- .join(',');
- if (!ids) return '';
- try {
- const res = await cameraList({ devIdList: ids, pageNo: 1, pageSize: pagination.pageSize });
- if (res?.records?.length) return ids;
- console.warn('[DeviceView] 按勾选设备ID查询无结果,退回全部摄像头。devIdList =', ids);
- } catch (e) {
- console.warn('[DeviceView] 按勾选设备ID查询异常,退回全部摄像头', e);
- }
- return '';
- }
- /** 加载当前页监控画面 */
- async function loadCameraList() {
- syncPaginationPlayRange();
- const devIdList = await resolveUsableDevIdList(props.deviceId);
- await getCamera(devIdList, playerRef, renderPlayer, pagination);
- if (streamPlayMode.value) {
- await nextTick();
- // 等待播放器挂载完成后再校正播放状态
- setTimeout(() => {
- applyStreamPlayState();
- applyViewLensStyles();
- }, 300);
- } else {
- await nextTick();
- setTimeout(() => applyViewLensStyles(), 300);
- }
- }
- /** 分页切换 */
- async function onPageChange(page: number) {
- pagination.current = page;
- selectedIndex.value = -1;
- selectedId.value = '';
- Object.keys(viewZoomMap).forEach((key) => delete viewZoomMap[Number(key)]);
- Object.keys(viewFocusBlurMap).forEach((key) => delete viewFocusBlurMap[Number(key)]);
- Object.keys(viewIrisBrightMap).forEach((key) => delete viewIrisBrightMap[Number(key)]);
- await loadCameraList();
- }
- onMounted(async () => {
- await loadCameraList();
- });
- watch(
- () => props.deviceId,
- async () => {
- pagination.current = 1;
- selectedIndex.value = -1;
- selectedId.value = '';
- Object.keys(viewZoomMap).forEach((key) => delete viewZoomMap[Number(key)]);
- Object.keys(viewFocusBlurMap).forEach((key) => delete viewFocusBlurMap[Number(key)]);
- Object.keys(viewIrisBrightMap).forEach((key) => delete viewIrisBrightMap[Number(key)]);
- await loadCameraList();
- }
- );
- onBeforeUnmount(() => {
- removeCamera(playerRef);
- });
- defineExpose({
- enableVisibleLightPlayMode,
- enableInfraredPlayMode,
- applyStreamPlayState,
- zoomSelectedView,
- focusSelectedView,
- irisSelectedView,
- openRegionPreview,
- });
- </script>
- <style lang="less" scoped>
- @import '/@/design/theme.less';
- // 设备画面主容器
- .device-view {
- --image-bg: url('@/assets/images/home-container/configurable/hsq/7-5.png');
- --dv-accent: #00d4ff;
- --dv-accent-soft: rgba(0, 212, 255, 0.45);
- --dv-text: #d8f4ff;
- position: relative;
- width: 100%;
- height: 100%;
- padding: 15px;
- box-sizing: border-box;
- }
- .device-view-content {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- color: var(--dv-text);
- user-select: none;
- // margin: 15px;
- }
- // 顶部操作按钮栏
- .view-actions {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 12px;
- margin-bottom: 14px;
- flex-shrink: 0;
- .action-btn {
- min-width: 96px;
- height: 30px;
- padding: 0 14px;
- border: 1px solid #269ee1;
- border-radius: 2px;
- background: #185f8e;
- color: #fff;
- font-size: 13px;
- letter-spacing: 0.5px;
- cursor: pointer;
- box-shadow: 0 0 6px rgba(0, 200, 255, 0.25), inset 0 0 8px rgba(0, 120, 180, 0.12);
- transition: color 0.2s, background 0.2s, box-shadow 0.2s;
- &:hover,
- &.active {
- color: #fff;
- background: rgba(0, 90, 140, 0.45);
- }
- &:active {
- transform: translateY(1px);
- }
- }
- }
- // 2×4 监控画面网格
- .view-grid {
- position: relative;
- flex: 1;
- min-height: 0;
- display: flex;
- overflow: hidden;
- }
- .view-cell {
- flex: 1;
- min-height: 0;
- cursor: pointer;
- }
- .media-wrap {
- height: 100%;
- box-sizing: border-box;
- }
- .view-pagination {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- flex-shrink: 0;
- height: 36px;
- padding: 0 4px;
- }
- :deep(.zxm-pagination) {
- color: #cfe9ff;
- .zxm-pagination-item,
- .zxm-pagination-prev,
- .zxm-pagination-next,
- .zxm-pagination-jump-prev,
- .zxm-pagination-jump-next {
- min-width: 26px;
- height: 26px;
- line-height: 24px;
- border: 1px solid rgba(58, 225, 255, 0.45);
- background: rgba(16, 64, 100, 0.85);
- color: #cfe9ff;
- }
- .zxm-pagination-item a,
- .zxm-pagination-prev button,
- .zxm-pagination-next button {
- color: #cfe9ff;
- }
- .zxm-pagination-item-active {
- border-color: #01fefc;
- background: rgba(32, 166, 169, 0.85);
- a {
- color: #fff;
- }
- }
- .zxm-pagination-disabled .zxm-pagination-item-link,
- .zxm-pagination-disabled button {
- color: rgba(207, 233, 255, 0.35);
- border-color: rgba(58, 225, 255, 0.2);
- }
- }
- // 无画面占位提示
- .media-empty {
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- :deep(#LivePlayerBox) {
- display: flex;
- flex-direction: row;
- justify-content: flex-start;
- flex-wrap: wrap;
- pointer-events: none;
- gap: 10px;
- .live-video-item {
- width: 349px;
- height: 220px;
- padding: 12px 8px;
- box-sizing: border-box;
- pointer-events: auto;
- cursor: pointer;
- background-size: 100% 100%;
- border: 1px solid transparent;
- border-radius: 2px;
- transition: border-color 0.2s, box-shadow 0.2s, background-color 0.2s;
- /* 变焦时容器尺寸不变,仅裁剪放大后的画面 */
- overflow: hidden;
- position: relative;
- flex: 0 0 349px;
- &:hover {
- border-color: rgba(1, 254, 252, 0.45);
- box-shadow: 0 0 8px rgba(0, 183, 255, 0.25);
- }
- /* 选中态:青色描边高亮,与监控界面风格一致 */
- &.is-selected {
- border-color: #01fefc;
- box-shadow: 0 0 10px rgba(1, 254, 252, 0.45), inset 0 0 12px rgba(0, 183, 255, 0.12);
- background-color: rgba(0, 90, 140, 0.18);
- }
- /* 内层媒体区:占满内容区,作为裁剪视口 */
- .liveVideo,
- video,
- canvas {
- display: block;
- width: 100% !important;
- height: 100% !important;
- max-width: 100%;
- max-height: 100%;
- object-fit: cover;
- transform-origin: center center;
- }
- }
- .liveVideo {
- width: 100% !important;
- height: 100% !important;
- padding: 0 !important;
- align-self: auto !important;
- background-size: 100% 100% !important;
- pointer-events: none;
- overflow: hidden;
- }
- .video-parent {
- pointer-events: auto !important;
- align-self: auto !important;
- overflow: hidden;
- width: 100%;
- height: 100%;
- }
- }
- ::-webkit-scrollbar {
- display: none;
- }
- /* 最新热源 / 最新闯入 弹窗(风格对齐报警自动弹窗) */
- .latest-alarm-mask {
- position: fixed;
- inset: 0;
- z-index: 2200;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(0, 8, 20, 0.55);
- }
- .latest-alarm-panel {
- width: min(720px, 92vw);
- max-height: 90vh;
- &.expanded {
- width: min(1080px, 96vw);
- }
- }
- .latest-alarm-frame {
- position: relative;
- padding: 18px 12px 0;
- background: linear-gradient(180deg, rgba(6, 28, 48, 0.98) 0%, rgba(4, 16, 32, 0.98) 100%);
- border: 1px solid rgba(0, 210, 255, 0.85);
- box-shadow: 0 0 0 2px rgba(8, 40, 70, 0.95), 0 0 22px rgba(0, 200, 255, 0.35), inset 0 0 0 1px rgba(0, 180, 255, 0.35);
- clip-path: polygon(10px 0, calc(100% - 10px) 0, 100% 10px, 100% calc(100% - 10px), calc(100% - 10px) 100%, 10px 100%, 0 calc(100% - 10px), 0 10px);
- }
- .latest-alarm-title-tab {
- position: absolute;
- top: -1px;
- left: 50%;
- transform: translateX(-50%);
- min-width: 168px;
- height: 28px;
- padding: 0 28px;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #fff;
- font-size: 14px;
- font-weight: 600;
- letter-spacing: 1px;
- background: linear-gradient(180deg, #2ad4e8 0%, #1498b8 55%, #0c6f8a 100%);
- clip-path: polygon(12px 0, calc(100% - 12px) 0, 100% 100%, 0 100%);
- box-shadow: 0 0 10px rgba(0, 200, 255, 0.45);
- z-index: 2;
- }
- .latest-alarm-toolbar {
- position: absolute;
- top: 8px;
- right: 12px;
- display: flex;
- gap: 8px;
- z-index: 3;
- }
- .latest-alarm-icon-btn {
- width: 22px;
- height: 22px;
- padding: 0;
- border: none;
- background: transparent;
- color: #e8f4ff;
- cursor: pointer;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- &:hover {
- color: #01fefc;
- }
- }
- .latest-alarm-image-wrap {
- position: relative;
- margin-top: 14px;
- height: 380px;
- border: 1px solid rgba(0, 183, 255, 0.45);
- background: #000;
- overflow: hidden;
- .latest-alarm-panel.expanded & {
- height: 560px;
- }
- }
- .latest-alarm-image {
- width: 100%;
- height: 100%;
- object-fit: contain;
- display: block;
- }
- .latest-alarm-image-empty {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- color: rgba(200, 220, 235, 0.55);
- font-size: 14px;
- }
- .latest-alarm-overlay {
- position: absolute;
- color: #fff;
- font-size: 13px;
- text-shadow: 0 0 4px rgba(0, 0, 0, 0.85);
- pointer-events: none;
- &.bottom-right {
- right: 10px;
- bottom: 10px;
- }
- }
- .latest-alarm-footer {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 8px;
- padding: 10px 8px 12px;
- color: #cfe9ff;
- font-size: 13px;
- }
- .latest-alarm-sep {
- color: rgba(1, 254, 252, 0.55);
- }
- </style>
|