| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000 |
- <!-- <template>
- <div class="screen-container">
- <div class="top-section">
- <div class="system-title">局部风机无人值守演示系统</div>
- <div class="top-inner">
- <div class="top-left">
- <div class="fan-switch">
- <label></label>
- <select v-model="selectFan" @change="switchFan">
- <option value="all">全部风机</option>
- <option v-for="fan in fanList" :key="fan.id" :value="fan.id">
- {{ fan.name }}
- </option>
- </select>
- <button class="btn btn-primary" @click="refreshVideo">刷新视频</button>
- <div class="time-box">当前时间:{{ currentTime }}</div>
- </div>
- <div class="video-single">
- <div class="video-item">
- <div class="video-title">风机实时监控 [{{ currentFanName }}]</div>
- <div class="video-box">
- <video class="video-bg" autoplay loop muted playsinline webkit-playsinline controls>
- <source src="@/assets/images/wind-video/video/fan-main.mp4" type="video/mp4" />
- </video>
- </div>
- </div>
- <div class="video-item alarm-video-replace">
- <div class="alarm-title">
- <span>井下设备操作日志</span>
- <div>
- <span class="alarm-badge">{{ operationLog.length }}</span>
- <button class="btn btn-sm" @click="clearLog">清空</button>
- </div>
- </div>
- <div class="alarm-scroll" ref="logScroll">
- <div class="alarm-item" v-for="(item, idx) in operationLog" :key="idx" :class="[item.status, item.new ? 'new' : '']">
- <div class="alarm-full-line">
- <span class="alarm-time">{{ item.time }}</span>
- <span class="alarm-fan">{{ item.fanName }}</span>
- <span class="alarm-type">{{ item.operateType }}</span>
- <span class="alarm-desc">{{ item.operator }} | {{ item.compareResult }}</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="top-right">
- <div class="status-panel">
- <div class="status-title">风机状态总览</div>
- <div class="status-grid">
- <div class="status-item normal">
- <div class="status-num">{{ statusData.normalFan }}</div>
- <div class="status-name">风机正常</div>
- </div>
- <div class="status-item offline">
- <div class="status-num">{{ statusData.offlineFan }}</div>
- <div class="status-name">风机离线</div>
- </div>
- <div class="status-item fault">
- <div class="status-num">{{ statusData.faultFan }}</div>
- <div class="status-name">风机故障</div>
- </div>
- <div class="status-item warn">
- <div class="status-num">{{ statusData.warn }}</div>
- <div class="status-name">实时预警</div>
- </div>
- <div class="status-item person-online">
- <div class="status-num">{{ statusData.realTimePerson }}</div>
- <div class="status-name">实时人数</div>
- </div>
- <div class="status-item person-total">
- <div class="status-num">{{ statusData.totalPerson }}</div>
- <div class="status-name">今日总进入</div>
- </div>
- </div>
- </div>
- <div class="area-stats-panel">
- <div class="status-title">区域停留统计</div>
- <div class="area-stats-list">
- <div class="area-stats-item" v-for="(item, idx) in areaStatsList" :key="idx">
- <div class="area-name">{{ item.areaName }}</div>
- <div class="area-data">
- <span class="person-count">{{ item.personCount }}人</span>
- <span class="stay-time">平均{{ item.avgStayTime }}</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="middle-section">
- <div class="search-panel">
- <div class="search-item">
- <label>时间范围:</label>
- <input type="datetime-local" v-model="searchForm.startTime" class="search-input" />
- <span class="split">-</span>
- <input type="datetime-local" v-model="searchForm.endTime" class="search-input" />
- <button class="btn btn-sm btn-default" @click="selectToday">今日</button>
- <button class="btn btn-sm btn-default" @click="select7d">近7天</button>
- </div>
- <div class="search-item">
- <label>操作类型:</label>
- <select v-model="searchForm.operateType" class="search-select">
- <option value="all">全部操作</option>
- <option value="风机调频">风机调频</option>
- <option value="风机切换">风机切换</option>
- <option value="参数调整">参数调整</option>
- <option value="复位操作">复位操作</option>
- </select>
- </div>
- <div class="search-item">
- <label>风机选择:</label>
- <select v-model="searchForm.fanId" class="search-select">
- <option value="all">全部风机</option>
- <option v-for="fan in fanList" :key="fan.id" :value="fan.id">{{ fan.name }}</option>
- </select>
- </div>
- <div class="search-btn-group">
- <button class="btn btn-primary" @click="handleSearch">一键检索</button>
- <button class="btn btn-default" @click="resetSearch">重置</button>
- </div>
- </div>
- </div>
- <div class="bottom-section">
- <div class="content-wrap">
- <div class="img-list">
- <div class="img-item" v-for="(item, idx) in currentImgList" :key="idx" @click="openImgModal(item)">
- <img :src="item.imgUrl" class="img-thumbnail" />
- <div class="img-desc">
- <p class="desc-line1">{{ getFanName(item.fanId) }}-{{ getAreaName(item.areaId) }}</p>
- <p class="desc-line2">{{ item.personName }} 停留{{ item.stayTime }}</p>
- </div>
- </div>
- </div>
- <div class="right-data-panel">
- <div class="tab-nav">
- <div class="tab-item" :class="{ active: activeTab === 'personTrack' }" @click="activeTab = 'personTrack'"> 人员轨迹</div>
- <div class="tab-item" :class="{ active: activeTab === 'areaStay' }" @click="activeTab = 'areaStay'">区域统计 </div>
- <div class="tab-item" :class="{ active: activeTab === 'warnRecord' }" @click="activeTab = 'warnRecord'">预警记录 </div>
- </div>
- <div class="data-list">
- <div class="data-header" v-if="activeTab === 'personTrack'">
- <div class="data-col">序号</div>
- <div class="data-col">风机</div>
- <div class="data-col">区域</div>
- <div class="data-col">姓名</div>
- <div class="data-col">区队</div>
- <div class="data-col">进入</div>
- <div class="data-col">离开</div>
- <div class="data-col">时长</div>
- </div>
- <div class="data-header" v-else-if="activeTab === 'areaStay'">
- <div class="data-col">序号</div>
- <div class="data-col">区域</div>
- <div class="data-col">人数</div>
- <div class="data-col">最长</div>
- <div class="data-col">最短</div>
- <div class="data-col">平均</div>
- <div class="data-col">总计</div>
- </div>
- <div class="data-header" v-else>
- <div class="data-col">序号</div>
- <div class="data-col">风机</div>
- <div class="data-col">区域</div>
- <div class="data-col">姓名</div>
- <div class="data-col">类型</div>
- <div class="data-col">时间</div>
- <div class="data-col">状态</div>
- </div>
- <div class="data-row" v-for="(item, idx) in personTrackList" :key="idx" v-if="activeTab === 'personTrack'">
- <div class="data-col">{{ idx + 1 }}</div>
- <div class="data-col">{{ getFanName(item.fanId) }}</div>
- <div class="data-col">{{ getAreaName(item.areaId) }}</div>
- <div class="data-col">{{ item.personName }}</div>
- <div class="data-col">{{ item.teamName }}</div>
- <div class="data-col">{{ item.enterTime }}</div>
- <div class="data-col">{{ item.leaveTime }}</div>
- <div class="data-col">{{ item.stayTime }}</div>
- </div>
- <div class="data-row" v-for="(item, idx) in areaStayList" :key="idx" v-if="activeTab === 'areaStay'">
- <div class="data-col">{{ idx + 1 }}</div>
- <div class="data-col">{{ item.areaName }}</div>
- <div class="data-col">{{ item.personCount }}</div>
- <div class="data-col">{{ item.maxStay }}</div>
- <div class="data-col">{{ item.minStay }}</div>
- <div class="data-col">{{ item.avgStay }}</div>
- <div class="data-col">{{ item.totalStay }}</div>
- </div>
- <div class="data-row" v-for="(item, idx) in warnRecordList" :key="idx" v-if="activeTab === 'warnRecord'">
- <div class="data-col">{{ idx + 1 }}</div>
- <div class="data-col">{{ getFanName(item.fanId) }}</div>
- <div class="data-col">{{ getAreaName(item.areaId) }}</div>
- <div class="data-col">{{ item.personName }}</div>
- <div class="data-col">{{ item.warnType }}</div>
- <div class="data-col">{{ item.warnTime }}</div>
- <div class="data-col" :class="item.handleStatus === '已处理' ? 'handled' : 'unhandled'">{{ item.handleStatus }}</div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="img-modal" v-show="showImgModal" @click="closeImgModal">
- <div class="modal-content" @click.stop>
- <div class="modal-header">
- <span>现场抓拍详情</span>
- <button class="close-btn" @click="closeImgModal">×</button>
- </div>
- <img :src="currentModalImg" class="modal-img" />
- <div class="modal-info">
- <p>{{ modalInfo.fanName }} - {{ modalInfo.areaName }}</p>
- <p>人员:{{ modalInfo.personName }} | 所属区队:{{ modalInfo.teamName }}</p>
- <p>进入时间:{{ modalInfo.enterTime }} | 离开时间:{{ modalInfo.leaveTime }} | 停留时长:{{ modalInfo.stayTime }}</p>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, onMounted, onUnmounted, nextTick } from 'vue';
- import {
- fanList,
- areaList,
- authPersons,
- statusData,
- areaStatsList,
- currentImgList,
- personTrackList,
- areaStayList,
- warnRecordList,
- } from './fanLocalVideo.data';
- // 工具方法
- const getFanName = (id) => fanList.find((i) => i.id === id)?.name || '未知风机';
- const getAreaName = (id) => areaList.find((i) => i.id === id)?.name || '未知区域';
- const selectFan = ref('all');
- const currentFanName = ref('1号掘进工作面局部风机');
- const currentTime = ref('');
- let timeTimer: null | NodeJS.Timeout = null;
- // ==============================================
- // 核心:井下设备操作日志(PLC + 配电柜 + 人员比对)
- // ==============================================
- const logScroll = ref<any>(null);
- let logTimer: null | NodeJS.Timeout = null;
- let addLogTimer: null | NodeJS.Timeout = null;
- // 操作日志列表(最多7条)
- const operationLog = ref([
- { time: '10:12:20', fanName: '1号风机', operateType: '风机调频', operator: '张三', compareResult: '人员准入匹配', status: 'green' },
- { time: '11:05:10', fanName: '2号风机', operateType: '参数调整', operator: '李四', compareResult: '人员准入匹配', status: 'green' },
- { time: '14:30:00', fanName: '3号风机', operateType: '切换主备', operator: '未知人员', compareResult: '未检测到人员', status: 'red' },
- ]);
- // 搜索
- const searchForm = ref({ startTime: '', endTime: '', operateType: 'all', fanId: 'all' });
- const activeTab = ref('personTrack');
- const showImgModal = ref(false);
- const currentModalImg = ref('');
- const modalInfo = ref({}) as any;
- // 生成PLC/配电柜操作记录 + 人员比对
- const createOperationLog = () => {
- const now = new Date();
- const time = `${String(now.getHours()).padStart(2, 0)}:${String(now.getMinutes()).padStart(2, 0)}:${String(now.getSeconds()).padStart(2, 0)}`;
- const fan = fanList[Math.floor(Math.random() * fanList.length)];
- const operateTypes = ['风机调频', '切换主备', '参数调整', '复位操作'];
- const operateType = operateTypes[Math.floor(Math.random() * operateTypes.length)];
- // 随机操作人员
- const allOps = [...authPersons, '未知人员', '外来人员'];
- const operator = allOps[Math.floor(Math.random() * allOps.length)];
- // 人员准入比对逻辑
- const compareResult = authPersons.includes(operator) ? '人员准入匹配' : '未检测到人员';
- const status = compareResult === '人员准入匹配' ? 'green' : 'red';
- return {
- time,
- fanName: fan.name,
- operateType,
- operator,
- compareResult,
- status,
- };
- };
- // 自动生成日志,最多保留7条
- const startAddOperationLog = () => {
- addLogTimer = setInterval(() => {
- const log = createOperationLog();
- operationLog.value.unshift(log);
- log.new = true;
- setTimeout(() => (log.new = false), 1800);
- // 最多7条
- if (operationLog.value.length > 7) {
- operationLog.value.pop();
- }
- }, 6000);
- };
- // 日志滚动
- const startLogScroll = () => {
- nextTick(() => {
- if (!logScroll.value) return;
- clearInterval(logTimer);
- logTimer = setInterval(() => {
- logScroll.value.scrollTop += 32;
- if (logScroll.value.scrollTop >= logScroll.value.scrollHeight - logScroll.value.clientHeight) {
- logScroll.value.scrollTop = 0;
- }
- }, 1500);
- });
- };
- // 清空日志
- const clearLog = () => {
- operationLog.value = [];
- };
- const switchFan = () => {
- if (selectFan.value !== 'all') currentFanName.value = getFanName(+selectFan.value);
- };
- const refreshVideo = () => {
- const v = document.querySelector('.video-bg');
- if (v) v.src = `/videos/fan-main.mp4?${Math.random()}`;
- };
- const formatDate = (d) => {
- const y = d.getFullYear();
- const m = String(d.getMonth() + 1).padStart(2, 0);
- const dt = String(d.getDate()).padStart(2, 0);
- const h = String(d.getHours()).padStart(2, 0);
- const mi = String(d.getMinutes()).padStart(2, 0);
- return `${y}-${m}-${dt}T${h}:${mi}`;
- };
- const selectToday = () => {
- const now = new Date();
- searchForm.value.startTime = formatDate(new Date(now.getFullYear(), now.getMonth(), now.getDate()));
- searchForm.value.endTime = formatDate(now);
- };
- const select7d = () => {
- const now = new Date();
- searchForm.value.startTime = formatDate(new Date(now - 7 * 86400000));
- searchForm.value.endTime = formatDate(now);
- };
- const handleSearch = () => alert('检索成功');
- const resetSearch = () => {
- searchForm.value = { startTime: '', endTime: '', operateType: 'all', fanId: 'all' };
- selectToday();
- };
- const openImgModal = (item) => {
- showImgModal.value = true;
- currentModalImg.value = item.imgUrl;
- modalInfo.value = {
- fanName: getFanName(item.fanId),
- areaName: getAreaName(item.areaId),
- personName: item.personName,
- teamName: item.teamName,
- enterTime: item.enterTime,
- leaveTime: item.leaveTime,
- stayTime: item.stayTime,
- };
- };
- const closeImgModal = () => (showImgModal.value = false);
- const updateTime = () => {
- const d = new Date();
- currentTime.value = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, 0)}-${String(d.getDate()).padStart(2, 0)} ${String(
- d.getHours()
- ).padStart(2, 0)}:${String(d.getMinutes()).padStart(2, 0)}:${String(d.getSeconds()).padStart(2, 0)}`;
- };
- onMounted(() => {
- selectToday();
- updateTime();
- timeTimer = setInterval(updateTime, 1000);
- startLogScroll();
- startAddOperationLog();
- });
- onUnmounted(() => {
- clearInterval(timeTimer);
- clearInterval(logTimer);
- clearInterval(addLogTimer);
- });
- </script>
- <style lang="less" scoped>
- .screen-container {
- width: 100%;
- height: 100%;
- background: #0a102b;
- color: #e0efff;
- font-family: 'Microsoft YaHei', sans-serif;
- overflow: hidden !important;
- padding: 8px;
- box-sizing: border-box;
- }
- .system-title {
- text-align: center;
- font-size: 28px;
- font-weight: bold;
- color: #49bbff;
- text-shadow: 0 0 15px #2a82ff;
- padding: 6px 0;
- letter-spacing: 2px;
- margin: 0 0 4px 0;
- }
- .top-section {
- height: 48%;
- border: 1px solid #1f4499;
- border-radius: 8px;
- background: rgba(8, 18, 48, 0.75);
- margin-bottom: 6px;
- overflow: hidden;
- box-sizing: border-box;
- }
- .top-inner {
- display: flex;
- height: calc(100% - 40px);
- padding: 6px 8px;
- gap: 10px;
- box-sizing: border-box;
- }
- .top-left {
- flex: 7;
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .top-right {
- flex: 3;
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .fan-switch {
- display: flex;
- align-items: center;
- height: 36px;
- gap: 10px;
- }
- .fan-switch select {
- height: 32px;
- padding: 0 8px;
- background: rgba(8, 18, 48, 0.8);
- border: 1px solid #2a5bda;
- color: #fff;
- border-radius: 4px;
- width: 280px;
- }
- .time-box {
- font-size: 15px;
- color: #49bbff;
- font-weight: 500;
- margin: auto;
- letter-spacing: 1px;
- }
- .video-single {
- display: flex;
- flex: 1;
- gap: 8px;
- }
- .video-item {
- flex: 1;
- border: 1px solid #2255cc;
- border-radius: 4px;
- background: #00000080;
- display: flex;
- flex-direction: column;
- height: 100%;
- }
- .video-title {
- height: 30px;
- line-height: 30px;
- padding-left: 8px;
- font-size: 13px;
- color: #9cc9ff;
- background: rgba(25, 55, 128, 0.3);
- }
- .video-box {
- position: relative;
- width: 100%;
- flex: 1;
- overflow: hidden;
- }
- .video-bg {
- width: 100%;
- height: 100%;
- object-fit: cover;
- opacity: 0.9;
- }
- .alarm-video-replace {
- display: flex;
- flex-direction: column;
- height: 100%;
- }
- .alarm-title {
- height: 30px;
- line-height: 30px;
- padding: 0 8px;
- background: rgba(25, 55, 128, 0.3);
- color: #9cc9ff;
- font-size: 13px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .alarm-scroll {
- flex: 1;
- padding: 4px;
- overflow: hidden;
- height: 100%;
- max-height: 100%;
- }
- .alarm-item {
- padding: 6px 8px;
- margin-bottom: 4px;
- border-radius: 4px;
- font-size: 12px;
- border-left: 3px solid transparent;
- display: block;
- }
- .alarm-full-line {
- width: 100%;
- display: block;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .alarm-time,
- .alarm-fan,
- .alarm-type,
- .alarm-desc {
- margin-right: 12px;
- line-height: 24px;
- }
- .alarm-desc {
- margin-right: 0;
- }
- /* 操作日志样式:匹配=绿色,不匹配=红色 */
- .alarm-item.green {
- background: rgba(0, 255, 100, 0.15);
- color: #9cffcc;
- border-left-color: #00ff66;
- }
- .alarm-item.red {
- background: rgba(255, 60, 60, 0.2);
- color: #ff9999;
- border-left-color: #ff4757;
- }
- .status-panel,
- .area-stats-panel {
- flex: 1;
- border: 1px solid #2255cc;
- border-radius: 4px;
- background: #00000060;
- display: flex;
- flex-direction: column;
- }
- .status-title {
- height: 30px;
- line-height: 30px;
- padding: 0 8px;
- background: rgba(25, 55, 128, 0.3);
- color: #9cc9ff;
- font-size: 13px;
- }
- .status-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- grid-template-rows: 1fr 1fr 1fr;
- gap: 4px;
- padding: 4px;
- flex: 1;
- }
- .status-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background: rgba(30, 65, 140, 0.2);
- border-radius: 4px;
- }
- .status-num {
- font-size: 20px;
- font-weight: bold;
- color: #fff;
- }
- .status-name {
- font-size: 11px;
- color: #9cc9ff;
- margin-top: 2px;
- }
- .area-stats-list {
- flex: 1;
- padding: 8px;
- display: flex;
- flex-direction: column;
- gap: 5px;
- overflow-y: auto;
- }
- .area-stats-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 6px 10px;
- background: rgba(30, 65, 140, 0.25);
- border-radius: 6px;
- font-size: 13px;
- }
- .area-name {
- color: #9cc9ff;
- width: 70px;
- }
- .area-data {
- display: flex;
- gap: 15px;
- }
- .person-count {
- color: #32ff80;
- font-weight: bold;
- }
- .stay-time {
- color: #ffa502;
- }
- .alarm-badge {
- display: inline-block;
- width: 18px;
- height: 18px;
- line-height: 18px;
- text-align: center;
- background: #49bbff;
- border-radius: 50%;
- font-size: 11px;
- color: #fff;
- margin-right: 6px;
- }
- .btn {
- padding: 5px 10px;
- background: rgba(42, 91, 218, 0.3);
- border: 1px solid #2a5bda;
- color: #fff;
- border-radius: 4px;
- cursor: pointer;
- }
- .btn-primary {
- background: #49bbff60;
- border-color: #49bbff;
- }
- .btn-sm {
- padding: 3px 6px;
- font-size: 11px;
- }
- .btn-default {
- background: rgba(30, 65, 140, 0.3);
- border-color: #1f4499;
- }
- .middle-section {
- height: 6%;
- border: 1px solid #1f4499;
- border-radius: 8px;
- background: rgba(8, 18, 48, 0.75);
- margin-bottom: 6px;
- display: flex;
- align-items: center;
- padding: 0 15px;
- box-sizing: border-box;
- }
- .search-panel {
- display: flex;
- align-items: center;
- gap: 15px;
- width: 100%;
- }
- .search-item {
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .search-item label {
- font-size: 13px;
- color: #9cc9ff;
- }
- .search-input,
- .search-select {
- height: 30px;
- padding: 0 6px;
- background: rgba(8, 18, 48, 0.8);
- border: 1px solid #2a5bda;
- color: #fff;
- border-radius: 4px;
- width: 160px;
- }
- .split {
- color: #9cc9ff;
- font-size: 12px;
- }
- .search-btn-group {
- margin-left: auto;
- display: flex;
- gap: 8px;
- }
- .bottom-section {
- height: 44%;
- border: 1px solid #1f4499;
- border-radius: 8px;
- background: rgba(8, 18, 48, 0.75);
- overflow: hidden;
- box-sizing: border-box;
- }
- .content-wrap {
- display: flex;
- height: 100%;
- padding: 8px;
- gap: 10px;
- box-sizing: border-box;
- }
- .img-list {
- flex: 2;
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- overflow-y: auto;
- padding: 4px;
- align-content: flex-start;
- }
- .right-data-panel {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 6px;
- }
- .img-item {
- width: calc(25% - 6px);
- border: 1px solid #2a5bda;
- border-radius: 4px;
- background: #00000060;
- padding: 4px;
- cursor: pointer;
- display: flex;
- flex-direction: column;
- height: 160px;
- }
- .img-thumbnail {
- width: 100%;
- height: 120px;
- object-fit: contain;
- border-radius: 4px;
- background: #000;
- }
- .img-desc {
- height: 32px;
- margin-top: 4px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- gap: 2px;
- }
- .desc-line1 {
- font-size: 10px;
- color: #9cc9ff;
- text-align: center;
- margin: 0;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .desc-line2 {
- font-size: 11px;
- color: #e0efff;
- text-align: center;
- margin: 0;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .tab-nav {
- display: flex;
- height: 32px;
- background: rgba(25, 55, 128, 0.2);
- border-radius: 4px;
- overflow: hidden;
- }
- .tab-item {
- flex: 1;
- line-height: 32px;
- text-align: center;
- font-size: 13px;
- color: #9cc9ff;
- cursor: pointer;
- }
- .tab-item.active {
- background: #49bbff40;
- color: #49bbff;
- border-bottom: 2px solid #49bbff;
- }
- .data-list {
- flex: 1;
- border: 1px solid #2a5bda;
- border-radius: 4px;
- background: #00000060;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- }
- .data-header {
- display: flex;
- height: 30px;
- line-height: 30px;
- background: rgba(25, 55, 128, 0.3);
- font-size: 12px;
- }
- .data-row {
- display: flex;
- height: 30px;
- line-height: 30px;
- border-bottom: 1px dashed #1f3e8a;
- font-size: 11px;
- color: #a0d3ff;
- }
- .data-col {
- flex: 1;
- text-align: center;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .data-col:first-child {
- flex: 0 1 50px;
- }
- .handled {
- color: #32ff80;
- font-weight: bold;
- }
- .unhandled {
- color: #ff4757;
- font-weight: bold;
- }
- .img-modal {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- background: rgba(0, 0, 0, 0.85);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 9999;
- }
- .modal-content {
- background: #0a102b;
- border: 2px solid #49bbff;
- border-radius: 8px;
- width: 700px;
- padding: 12px;
- }
- .modal-header {
- display: flex;
- justify-content: space-between;
- margin-bottom: 8px;
- font-size: 15px;
- color: #49bbff;
- }
- .close-btn {
- background: none;
- border: none;
- color: #fff;
- font-size: 20px;
- cursor: pointer;
- }
- .modal-img {
- width: 100%;
- height: 400px;
- object-fit: contain;
- background: #000;
- border-radius: 4px;
- }
- .modal-info {
- text-align: center;
- margin-top: 8px;
- font-size: 13px;
- color: #e0efff;
- }
- ::-webkit-scrollbar {
- width: 6px;
- height: 6px;
- }
- ::-webkit-scrollbar-track {
- background: rgba(25, 55, 128, 0.2);
- border-radius: 3px;
- }
- ::-webkit-scrollbar-thumb {
- background: #49bbff;
- border-radius: 3px;
- }
- @keyframes newAlarmFlash {
- 0% {
- background: rgba(73, 187, 255, 0.4);
- transform: scale(1.02);
- }
- 100% {
- background: initial;
- transform: scale(1);
- }
- }
- .alarm-item.new {
- animation: newAlarmFlash 1.8s forwards;
- }
- </style> -->
|