| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411 |
- <template>
- <div class="page" @mousemove="onMouseMove" @mouseleave="onMouseLeave">
- <!-- Top Header -->
- <header class="topbar">
- <div class="top-left">
- <div class="weather">
- <i class="dot-sun" aria-hidden="true"></i>
- <span class="w1">{{ header.weatherText }}</span>
- <span class="w2">{{ header.tempText }}</span>
- </div>
- </div>
- <div class="top-center" style="visibility: hidden;">
- <div class="title-frame">
- <div class="title">交通信号控制平台</div>
- </div>
- </div>
- <div class="top-right">
- <div class="clock">
- <div class="time">{{ header.timeText }}</div>
- <div class="date">
- <span>{{ header.weekText }}</span>
- <span class="sep">·</span>
- <span>{{ header.dateText }}</span>
- </div>
- </div>
- </div>
- </header>
- <!-- Main Layout -->
- <main class="grid">
- <!-- Left -->
- <section class="col left">
- <!-- 在线状态(2s循环:信号机/检测器/相机) -->
- <div class="card card-a">
- <div class="card-h">
- <span class="h-dot"></span>
- <span>在线状态</span>
- </div>
- <div class="tabs">
- <button
- v-for="t in onlineTabs"
- :key="t.key"
- class="tab"
- :class="{ active: onlineTab === t.key }"
- @click="setOnlineTab(t.key, true)"
- >
- {{ t.label }}
- </button>
- </div>
- <div class="row">
- <div ref="onlineChart" class="chart donut"></div>
- <div class="legend">
- <div class="lg">
- <span class="b b-on"></span><span>在线</span>
- <span class="num">{{ onlineView.online }}</span>
- </div>
- <div class="lg">
- <span class="b b-off"></span><span>离线</span>
- <span class="num">{{ onlineView.offline }}</span>
- </div>
- <div class="lg">
- <span class="b b-rate"></span><span>在线率</span>
- <span class="num">{{ onlineView.rate }}%</span>
- </div>
- <div class="subnote">
- {{ onlineTabsMap[onlineTab] }} · {{ onlineView.online }}/{{ onlineView.total }}
- </div>
- </div>
- <!-- <div class="dock-arrow right" @click="dockNext" title="下一页"></div> -->
- </div>
- </div>
- <!-- 控制模式(GB20999 / GAT1049) -->
- <div class="card card-b">
- <div class="card-h">
- <span class="h-dot"></span>
- <span>在线状态</span>
- </div>
- <div class="tabs small">
- <button class="tab" :class="{ active: true }">信号机</button>
- <button class="tab">检测器</button>
- <button class="tab">红绿灯</button>
- </div>
- <div class="row row-b">
- <div class="legend legend-b">
- <div class="lg" v-for="it in controlLegend" :key="it.k">
- <span class="b" :class="it.cls"></span><span>{{ it.t }}</span>
- <span class="num">{{ it.v }}</span>
- </div>
- </div>
- <div ref="controlChart" class="chart donut small"></div>
- </div>
- </div>
- <!-- 故障报警(展示4条,可忽略/查看,超过4条自动上移) -->
- <div class="card card-c grow">
- <div class="card-h">
- <span class="h-dot"></span>
- <span>故障报警</span>
- </div>
- <div class="alarm-list">
- <div class="alarm-item" v-for="a in alarmsView" :key="a.id">
- <div class="a-left">
- <div class="a-title">
- <span class="lvl" :class="'lv-' + a.level">{{ a.levelText }}</span>
- <span class="txt">{{ a.title }}</span>
- </div>
- <div class="a-sub">
- <span class="loc">{{ a.loc }}</span>
- <!-- <span class="time">{{ a.time }}</span> -->
- </div>
- </div>
- <div class="a-actions">
- <button class="btn ghost" @click="ignoreAlarm(a.id)">忽略</button>
- <button class="btn primary" @click="viewAlarm(a)">查看</button>
- </div>
- </div>
- <div v-if="alarmsView.length === 0" class="empty">暂无告警</div>
- </div>
- </div>
- </section>
- <!-- Middle -->
- <section class="col mid">
- <div class="map-wrap">
- <div class="map-frame">
- <!-- Top tools -->
- <div class="map-tools">
- <div class="search">
- <input v-model="mapQuery" placeholder="查询" />
- <button class="btn icon" @click="doSearch">查询</button>
- </div>
- <div class="select">
- <span class="lbl">全选</span>
- <span class="caret">▾</span>
- </div>
- </div>
- <!-- Map placeholder (可替换背景图) -->
- <div class="map-canvas" :style="mapBgStyle">
- <!-- roads overlay (placeholder) -->
- <svg class="roads" viewBox="0 0 1000 560" preserveAspectRatio="none" aria-hidden="true">
- <path class="r g" d="M80 410 C260 420, 380 360, 520 360 C660 360, 820 420, 940 420"/>
- <path class="r g" d="M120 150 C240 150, 310 190, 420 210 C520 230, 640 210, 820 150"/>
- <path class="r b" d="M520 60 L520 520"/>
- <path class="r g" d="M350 80 L350 520"/>
- <path class="r y" d="M170 330 L840 330"/>
- <path class="r red" d="M700 220 C720 260, 700 300, 680 330"/>
- <g class="nodes">
- <circle v-for="n in 10" :key="n" :cx="520" :cy="60 + n*42" r="6" class="node"/>
- </g>
- </svg>
- <!-- Popup (from alarm view) -->
- <div v-if="mapPopup" class="popup">
- <div class="popup-title">{{ mapPopup.title }}</div>
- <div class="popup-line">路口:{{ mapPopup.loc }}</div>
- <div class="popup-line">发生时间:{{ mapPopup.time }}</div>
- </div>
- <!-- Legend -->
- <div class="legend-box">
- <div class="legend-title">图例</div>
- <div class="legend-row" v-for="it in legendItems" :key="it.k">
- <span class="chip" :class="it.cls"></span>
- <span class="legend-t">{{ it.t }}</span>
- </div>
- </div>
- </div>
- <!-- Bottom Dock (overlay on map) -->
- <div class="dock-wrap" @mousemove="onDockMove" @mouseleave="onDockLeave">
- <div class="dock-arrow left" @click="dockPrev" title="上一页"></div>
- <div class="dockbar">
- <div
- v-for="(m, idx) in modules"
- :key="m.key"
- class="dock-item"
- :class="{ active: activeModule === m.key }"
- :style="navItemStyle(idx)"
- @click="selectModule(m)"
- >
- <div class="dock-icon" :style="navIconStyle(m)"></div>
- <div class="dock-label">{{ m.title }}</div>
- </div>
- </div>
- <div class="dock-arrow right" @click="dockNext" title="下一页"></div>
- </div>
- </div>
- </div>
- </section>
- <!-- Right -->
- <section class="col right">
- <!-- 设备状态(2s循环:信号机/检测器/红绿灯) -->
- <div class="card card-a">
- <div class="card-h">
- <span class="h-dot"></span>
- <span>设备状态</span>
- </div>
- <div class="tabs">
- <button
- v-for="t in deviceTabs"
- :key="t.key"
- class="tab"
- :class="{ active: deviceTab === t.key }"
- @click="setDeviceTab(t.key, true)"
- >
- {{ t.label }}
- </button>
- </div>
- <div class="row">
- <div ref="deviceChart" class="chart donut"></div>
- <div class="legend">
- <div class="lg">
- <span class="b b-ok"></span><span>正常</span>
- <span class="num">{{ deviceView.normal }}</span>
- </div>
- <div class="lg">
- <span class="b b-bad"></span><span>故障</span>
- <span class="num">{{ deviceView.fault }}</span>
- </div>
- <div class="subnote">
- {{ deviceTabsMap[deviceTab] }} · {{ deviceView.fault === 0 ? "全绿" : "需处理" }}
- </div>
- </div>
- <!-- <div class="dock-arrow right" @click="dockNext" title="下一页"></div> -->
- </div>
- </div>
- <!-- 勤务执行(>5滚动,点击可高亮地图) -->
- <div class="card card-c grow">
- <div class="card-h">
- <span class="h-dot"></span>
- <span>勤务执行</span>
- </div>
- <div class="tbl-wrap">
- <table class="tbl">
- <thead>
- <tr>
- <th style="width: 12%;">序号</th>
- <th style="width: 30%;">名称</th>
- <th style="width: 15%;">执行人</th>
- <th style="width: 15%;">等级</th>
- <th style="width: 15%;">状态</th>
- <th style="width: 13%;">操作</th>
- </tr>
- </thead>
- <tbody :style="dutyScrollStyle">
- <tr v-for="(r,i) in duty" :key="r.id" :class="{ hl: activeDutyId === r.id }">
- <td>{{ i + 1 }}</td>
- <td :title="r.name" class="name">{{ r.name }}</td>
- <td>{{ r.executor }}</td>
- <td><span class="tag" :class="'lv' + r.level">{{ r.levelText }}</span></td>
- <td><span class="st" :class="r.statusKey">{{ r.status }}</span></td>
- <td><a class="link" href="javascript:;" @click="goDuty(r)">查看</a></td>
- </tr>
- </tbody>
- </table>
- </div>
- </div>
- <!-- 关键路口(保留原平台设计界面:占位结构) -->
- <div class="card card-b">
- <div class="card-h">
- <span class="h-dot"></span>
- <span>关键路口</span>
- </div>
- <table class="tbl simple">
- <thead>
- <tr>
- <th>路口</th>
- <th style="width:98px;">运营模式</th>
- <th style="width:68px;">方案号</th>
- </tr>
- </thead>
- <tbody>
- <tr v-for="k in keyJunctions" :key="k.id">
- <td class="name">{{ k.name }}</td>
- <td>{{ k.mode }}</td>
- <td>{{ k.plan }}</td>
- </tr>
- </tbody>
- </table>
- </div>
- </section>
- </main>
- </div>
- </template>
- <script>
- import * as echarts from "echarts";
- export default {
- // eslint-disable-next-line vue/multi-word-component-names
- name: "Home",
- data() {
- return {
- baseW: 1920,
- baseH: 1080,
- scale: 1,
- mouseX: null,
- mouseY: null,
- activeDockIndex: 0,
- header: {
- weatherText: "晴",
- tempText: "32/17°C",
- timeText: "--:--:--",
- dateText: "--",
- weekText: "周五"
- },
- mapQuery: "",
- mapPopup: null,
- mapBgUrl: "", // 后期替换真实底图:在这里填 url
- legendItems: [
- { k: "center", t: "中心计划", cls: "c1" },
- { k: "coord", t: "干线协调", cls: "c2" },
- { k: "bus", t: "勤务路线", cls: "c3" },
- { k: "cycle", t: "定周期控制", cls: "c4" },
- { k: "sense", t: "感应控制", cls: "c5" },
- { k: "self", t: "自适应控制", cls: "c6" },
- { k: "manual", t: "手动控制", cls: "c7" },
- { k: "spec", t: "特殊控制", cls: "c8" },
- { k: "offline", t: "离线", cls: "c9" },
- { k: "drop", t: "降级", cls: "c10" },
- { k: "fault", t: "故障", cls: "c11" }
- ],
- onlineTab: "signal",
- onlineTabs: [
- { key: "signal", label: "信号机" },
- { key: "detector", label: "检测器" },
- { key: "camera", label: "相机" }
- ],
- onlineTabsMap: { signal: "信号机", detector: "检测器", camera: "相机" },
- onlineData: {
- signal: { online: 980, offline: 20, total: 1000, rate: 98 },
- detector: { online: 461, offline: 39, total: 500, rate: 92 },
- camera: { online: 298, offline: 12, total: 310, rate: 96 }
- },
-
- // 左侧第二块:控制信息(示例图的环形图)
- controlLegend: [
- { k: "cycle", t: "定周期控制", cls: "c4", v: 400 },
- { k: "sense", t: "感应控制", cls: "c5", v: 50 },
- { k: "coord", t: "干线协调", cls: "c2", v: 200 },
- { k: "spec", t: "黄闪控制", cls: "c8", v: 5 }
- ],
- controlTotal: 650,
- deviceTab: "signal",
- deviceTabs: [
- { key: "signal", label: "信号机" },
- { key: "detector", label: "检测器" },
- { key: "lamp", label: "红绿灯" }
- ],
- deviceTabsMap: { signal: "信号机", detector: "检测器", lamp: "红绿灯" },
- deviceData: {
- signal: { normal: 128, fault: 0 },
- detector: { normal: 89, fault: 2 },
- lamp: { normal: 312, fault: 4 }
- },
- alarms: [
- { id: "a1", level: 1, levelText: "一级", title: "通讯中断", loc: "中关村大街-科学院南路口-设备离线", time: "16:28:28" },
- { id: "a2", level: 2, levelText: "二级", title: "降级黄闪", loc: "中关村大街-科学院南路口-设备离线", time: "16:28:28" },
- { id: "a3", level: 3, levelText: "三级", title: "信号灯报警", loc: "知春路-学院路口-信号灯异常", time: "16:18:02" }
- ],
- duty: [
- { id: "d1", name: "大型活动交通安保", executor: "赵刚", level: 1, levelText: "一级", statusKey: "wait", status: "未开始" },
- { id: "d2", name: "道路施工路段交通引导", executor: "林小宇", level: 1, levelText: "一级", statusKey: "wait", status: "未开始" },
- { id: "d3", name: "酒驾醉驾专项查缉", executor: "周婷", level: 2, levelText: "二级", statusKey: "doing", status: "进行中" },
- { id: "d4", name: "交通信号灯故障排查", executor: "吴磊", level: 1, levelText: "一级", statusKey: "wait", status: "未开始" },
- { id: "d5", name: "应急救援通道清障", executor: "郑晓东", level: 1, levelText: "一级", statusKey: "wait", status: "未开始" }
- ],
- dutyScrollOffset: 0,
- activeDutyId: "",
- keyJunctions: [
- { id: "k1", name: "实行东街铁双园路交叉路口", mode: "定周期控制", plan: 4 },
- { id: "k2", name: "实行东街铁双园路交叉路口", mode: "自适应控制", plan: 1 },
- { id: "k3", name: "实行东街铁双园路交叉路口", mode: "感应控制", plan: 5 }
- ],
- modules: [
- { key: "home", title: "首页", img: "main-home.png", route: { path: "/home" } },
- { key: "watch", title: "状态监控", img: "main-watch.png", route: { path: "/home", query: { panel: "watch" } } },
- { key: "vip", title: "特勤安保", img: "main-security.png", route: { path: "/home", query: { panel: "security" } } },
- { key: "corr", title: "干线协调", img: "main-coor.png", route: { path: "/home", query: { panel: "coor" } } },
- { key: "overview", title: "状态展示", img: "main-surve.png", route: { path: "/home", query: { panel: "surve" } } },
- { key: "settings", title: "系统设置", img: "main-setting.png", route: { path: "/home", query: { panel: "setting" } } }
- ],
- activeModule: "home",
- hoverModule: "",
- onlineChart: null,
- controlChart: null,
- deviceChart: null,
- timerClock: null,
- timerCycle: null,
- timerDutyScroll: null,
- cycleIdx: 0,
- deviceCycleIdx: 0
- };
- },
- computed: {
- onlineView() {
- return this.onlineData[this.onlineTab] || { online: 0, offline: 0, total: 0, rate: 0 };
- },
- deviceView() {
- return this.deviceData[this.deviceTab] || { normal: 0, fault: 0 };
- },
- alarmsView() {
- const arr = [...this.alarms].sort((a, b) =>
- a.level !== b.level ? a.level - b.level : (b.time || "").localeCompare(a.time || "")
- );
- return arr.slice(0, 4);
- },
- mapBgStyle() {
- if (!this.mapBgUrl) return {};
- return { backgroundImage: `url(${this.mapBgUrl})` };
- },
- dutyScrollStyle() {
- if (this.duty.length <= 5) return {};
- return { transform: `translateY(${-this.dutyScrollOffset}px)` };
- }
- },
- mounted() {
- this.updateScale();
- window.addEventListener("resize", this.updateScale, { passive: true });
- this.startClock();
- this.initCharts();
- this.renderCharts();
- this.timerCycle = setInterval(() => {
- this.cycleIdx = (this.cycleIdx + 1) % this.onlineTabs.length;
- this.setOnlineTab(this.onlineTabs[this.cycleIdx].key, false);
- this.deviceCycleIdx = (this.deviceCycleIdx + 1) % this.deviceTabs.length;
- this.setDeviceTab(this.deviceTabs[this.deviceCycleIdx].key, false);
- }, 2000);
- this.timerDutyScroll = setInterval(() => {
- if (this.duty.length <= 5) return;
- this.dutyScrollOffset += 34;
- const max = (this.duty.length - 5) * 34;
- if (this.dutyScrollOffset > max) this.dutyScrollOffset = 0;
- }, 2200);
- window.addEventListener("resize", this.onResize, { passive: true });
- },
- beforeDestroy() {
- window.removeEventListener("resize", this.updateScale);
- window.removeEventListener("resize", this.onResize);
- clearInterval(this.timerClock);
- clearInterval(this.timerCycle);
- clearInterval(this.timerDutyScroll);
- this.onlineChart && this.onlineChart.dispose();
- this.controlChart && this.controlChart.dispose();
- this.deviceChart && this.deviceChart.dispose();
- },
- methods: {
- onMouseMove(e) {
- const rect = this.$el.getBoundingClientRect();
- this.mouseX = e.clientX - rect.left;
- this.mouseY = e.clientY - rect.top;
- },
- onMouseLeave() {
- this.mouseX = null;
- this.mouseY = null;
- },
- updateScale() {
- const w = window.innerWidth || this.baseW;
- const h = window.innerHeight || this.baseH;
- this.scale = Math.min(w / this.baseW, h / this.baseH);
- this.$el && this.$el.style.setProperty("--s", this.scale.toFixed(6));
- },
- onDockMove(e) {
- const rect = this.$el.getBoundingClientRect();
- this.mouseX = e.clientX - rect.left;
- this.mouseY = e.clientY - rect.top;
- },
- onDockLeave() {
- this.mouseX = null;
- this.mouseY = null;
- },
- navItemStyle(idx) {
- if (this.mouseX == null) return { transform: "translateZ(0) scale(1)" };
- const dock = this.$el.querySelector(".dockbar");
- const el = dock && dock.children && dock.children[idx];
- if (!el) return { transform: "translateZ(0) scale(1)" };
- const r = el.getBoundingClientRect();
- const root = this.$el.getBoundingClientRect();
- const cx = (r.left - root.left) + r.width / 2;
- const cy = (r.top - root.top) + r.height / 2;
- const dx = this.mouseX - cx;
- const dy = this.mouseY - cy;
- const dist = Math.sqrt(dx * dx + dy * dy);
- const R = 220 * this.scale;
- const t = Math.max(0, 1 - dist / R);
- const s = 1 + 0.35 * (t * t);
- return { transform: `translateZ(0) scale(${s.toFixed(4)})` };
- },
- onResize() {
- this.onlineChart && this.onlineChart.resize();
- this.controlChart && this.controlChart.resize();
- this.deviceChart && this.deviceChart.resize();
- },
- startClock() {
- const tick = () => {
- const d = new Date();
- const pad = (n) => String(n).padStart(2, "0");
- this.header.timeText = `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
- this.header.dateText = `${d.getFullYear()}.${pad(d.getMonth() + 1)}.${pad(d.getDate())}`;
- const wk = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"][d.getDay()];
- this.header.weekText = wk;
- };
- tick();
- this.timerClock = setInterval(tick, 1000);
- },
- initCharts() {
- this.onlineChart = echarts.init(this.$refs.onlineChart);
- this.controlChart = echarts.init(this.$refs.controlChart);
- this.deviceChart = echarts.init(this.$refs.deviceChart);
- },
- renderCharts() {
- this.renderOnlineChart();
- this.renderControlChart();
- this.renderDeviceChart();
- },
- renderOnlineChart() {
- const v = this.onlineView;
- this.onlineChart.setOption({
- animation: true,
- series: [
- {
- type: "pie",
- radius: ["70%", "88%"],
- center: ["50%", "50%"],
- silent: true,
- label: { show: false },
- labelLine: { show: false },
- data: [
- { value: v.online, name: "在线" },
- { value: Math.max(0, v.total - v.online), name: "离线" }
- ]
- }
- ],
- graphic: [
- {
- type: "text",
- left: "center",
- top: "40%",
- style: {
- text: `${v.rate}%`,
- fill: "rgba(235,255,255,0.92)",
- fontSize: 22,
- fontWeight: 800
- }
- },
- {
- type: "text",
- left: "center",
- top: "58%",
- style: {
- text: `${v.online}/${v.total}`,
- fill: "rgba(190,225,255,0.70)",
- fontSize: 12,
- fontWeight: 600
- }
- }
- ]
- });
- },
- renderControlChart() {
- const items = this.controlLegend || [];
- const total = this.controlTotal || items.reduce((s, x) => s + (x.v || 0), 0) || 0;
- this.controlChart.setOption({
- animation: true,
- series: [
- {
- type: "pie",
- radius: ["72%", "90%"],
- center: ["50%", "50%"],
- silent: true,
- label: { show: false },
- labelLine: { show: false },
- data: items.map((x) => ({ value: x.v, name: x.t }))
- }
- ],
- graphic: [
- {
- type: "text",
- left: "center",
- top: "40%",
- style: {
- text: `${total}个`,
- fill: "rgba(235,255,255,0.92)",
- fontSize: 18,
- fontWeight: 800
- }
- },
- {
- type: "text",
- left: "center",
- top: "58%",
- style: {
- text: "控制信息",
- fill: "rgba(190,225,255,0.70)",
- fontSize: 12,
- fontWeight: 600
- }
- }
- ]
- });
- },
- renderDeviceChart() {
- const v = this.deviceView;
- this.deviceChart.setOption({
- animation: true,
- series: [
- {
- type: "pie",
- radius: ["70%", "88%"],
- center: ["50%", "50%"],
- silent: true,
- label: { show: false },
- labelLine: { show: false },
- data: [
- { value: v.fault, name: "故障" },
- { value: Math.max(0, v.normal), name: "正常" }
- ]
- }
- ],
- graphic: [
- {
- type: "text",
- left: "center",
- top: "45%",
- style: {
- text: v.fault === 0 ? "故障 0" : `故障 ${v.fault}`,
- fill: "rgba(235,255,255,0.92)",
- fontSize: 18,
- fontWeight: 800
- }
- }
- ]
- });
- },
- setOnlineTab(key, byUser) {
- this.onlineTab = key;
- this.renderOnlineChart();
- if (byUser) this.cycleIdx = this.onlineTabs.findIndex((x) => x.key === key);
- },
- setDeviceTab(key, byUser) {
- this.deviceTab = key;
- this.renderDeviceChart();
- if (byUser) this.deviceCycleIdx = this.deviceTabs.findIndex((x) => x.key === key);
- },
- ignoreAlarm(id) {
- this.alarms = this.alarms.filter((x) => x.id !== id);
- },
- viewAlarm(a) {
- this.mapPopup = { title: a.title, loc: a.loc, time: a.time };
- setTimeout(() => {
- if (this.mapPopup && this.mapPopup.title === a.title) this.mapPopup = null;
- }, 3000);
- },
- goDuty(r) {
- this.activeDutyId = r.id;
- this.mapPopup = {
- title: "勤务任务提醒",
- loc: r.name,
- time: this.header.dateText + " " + this.header.timeText
- };
- },
- doSearch() {
- this.mapPopup = {
- title: "查询结果",
- loc: this.mapQuery || "(未输入)",
- time: this.header.dateText + " " + this.header.timeText
- };
- setTimeout(() => (this.mapPopup = null), 2200);
- },
- assetUrl(file) {
- try {
- return require("@/assets/main/" + file);
- } catch (e) {
- return "";
- }
- },
- navIconStyle(m) {
- return { backgroundImage: `url(${this.assetUrl(m.img)})` };
- },
- selectModule(m) {
- this.activeModule = m.key;
- if (m.route && this.$router) this.$router.push(m.route);
- },
- dockPrev() {
- if (typeof this.activeDockIndex !== "number") this.activeDockIndex = 0;
- const n = (this.dockItems && this.dockItems.length) ? this.dockItems.length : 6;
- this.activeDockIndex = (this.activeDockIndex - 1 + n) % n;
- },
- dockNext() {
- if (typeof this.activeDockIndex !== "number") this.activeDockIndex = 0;
- const n = (this.dockItems && this.dockItems.length) ? this.dockItems.length : 6;
- this.activeDockIndex = (this.activeDockIndex + 1) % n;
- },
- }
- };
- </script>
- <style scoped>
- /* ====== Root ====== */
- .page{
- width:100vw;
- height:100vh;
- overflow:hidden;
- background: radial-gradient(1200px 700px at 50% 25%, rgba(40,120,255,0.22), rgba(5,12,30,1) 58%);
- position:relative;
- --s: 1;
- font-family: "Microsoft YaHei", system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
- display:flex;
- flex-direction:column;
- }
- /* ====== Top ====== */
- .topbar{
- height: calc(var(--s) * 100px);
- display:grid;
- grid-template-columns: 1fr 1.6fr 1fr;
- align-items:center;
- padding: 0 calc(var(--s) * 26px);
- position:relative;
- background: center/100% 100% no-repeat;
- background-image: url("~@/assets/main/main-header.png");
- z-index: 10;
- }
- .topbar::before{ display:none; }
- .title-frame{
- width: min(62vw, calc(var(--s) * 980px));
- height: calc(var(--s) * 64px);
- border-radius: calc(var(--s) * 14px);
- background: rgba(10,35,70,0.35);
- box-shadow: inset 0 0 calc(var(--s) * 26px) rgba(0,160,255,0.18);
- display:flex;
- align-items:center;
- justify-content:center;
- }
- .title{
- font-size: calc(var(--s) * 34px);
- letter-spacing: calc(var(--s) * 2px);
- text-shadow: 0 0 calc(var(--s) * 10px) rgba(90, 200, 255, 0.35);
- font-weight: 600;
- }
- .clock{ text-align:right; color: rgba(235,255,255,0.92); }
- .time{ font-weight: 900; font-size: clamp(18px, 1.6vw, 28px); letter-spacing: 1px; }
- .date{ margin-top:4px; font-size: 12px; color: rgba(190,225,255,0.78); }
- .sep{ margin: 0 6px; opacity: 0.6; }
- /* ====== Grid ====== */
- .grid{
- flex: 1;
- min-height: 0;
- padding: clamp(10px, 1.6vw, 18px);
- display:grid;
- grid-template-columns: 1fr 2.1fr 1fr;
- gap: clamp(10px, 1.2vw, 16px);
- }
- .col{ min-width:0; min-height:0; }
- .col.left, .col.right{ display:grid; grid-template-rows: 1fr 1fr 1fr; gap: clamp(10px, 1.2vw, 16px); }
- .col.mid{ display:flex; flex-direction:column; gap: clamp(10px, 1.2vw, 16px); }
- .grow{ flex:1; min-height:0; overflow:hidden; }
- /* ====== Cards ====== */
- .card{
- display:flex;
- flex-direction:column;
- min-height:0;
- border-radius: 14px;
- background: linear-gradient(180deg, rgba(8,22,60,0.58), rgba(8,22,60,0.28));
- border: 1px solid rgba(80,200,255,0.18);
- box-shadow: 0 0 24px rgba(0,160,255,0.08);
- padding: 12px 12px;
- position:relative;
- overflow:hidden;
- }
- .card::before{
- content:"";
- position:absolute;
- inset:0;
- background:
- radial-gradient(500px 220px at 30% 0%, rgba(0,220,255,0.16), rgba(0,0,0,0)),
- radial-gradient(420px 220px at 80% 100%, rgba(30,120,255,0.10), rgba(0,0,0,0));
- pointer-events:none;
- }
- .card-h{
- position:relative;
- z-index:1;
- display:flex;
- align-items:center;
- gap:10px;
- color: rgba(230,250,255,0.92);
- font-weight: 800;
- letter-spacing: 0.5px;
- margin-bottom: 10px;
- }
- .h-dot{
- width: 6px; height: 6px; border-radius: 50%;
- background: rgba(0,240,255,0.9);
- box-shadow: 0 0 12px rgba(0,240,255,0.55);
- }
- /* Tabs */
- .tabs{
- position:relative; z-index:1;
- display:flex;
- gap: 10px;
- margin-bottom: 10px;
- }
- .tab{
- flex:1;
- height: 28px;
- border-radius: 8px;
- border: 1px solid rgba(80,200,255,0.18);
- background: rgba(10,30,80,0.28);
- color: rgba(190,225,255,0.82);
- cursor:pointer;
- font-weight:700;
- font-size: 12px;
- }
- .tabs.small{ margin-bottom: 8px; }
- .tabs.small .tab{ height: 26px; font-size: 12px; }
- .tab.active{
- background: rgba(0,180,255,0.18);
- color: rgba(235,255,255,0.92);
- box-shadow: 0 0 16px rgba(0,200,255,0.14);
- }
- /* Charts */
- .row{ position:relative; z-index:1; display:grid; grid-template-columns: 140px 1fr; gap: 12px; align-items:center; }
- .chart.donut{ width: 140px; height: 120px; }
- .chart.donut.small{ width: 120px; height: 108px; }
- .row.row-b{ grid-template-columns: 1fr 120px; }
- .legend.legend-b{ gap: 8px; }
- .legend.legend-b .lg{ grid-template-columns: 14px 1fr auto; }
- .legend{ display:flex; flex-direction:column; gap: 8px; }
- .lg{ display:flex; align-items:center; gap: 8px; color: rgba(200,235,255,0.78); font-size: 12px; }
- .lg .num{ margin-left:auto; color: rgba(235,255,255,0.92); font-weight: 800; }
- .subnote{ margin-top: 2px; color: rgba(170,215,255,0.62); font-size: 12px; }
- .b{ width:8px; height:8px; border-radius: 2px; display:inline-block; }
- .b-on{ background: rgba(80,240,255,0.95); }
- .b-off{ background: rgba(255,200,60,0.85); }
- .b-rate{ background: rgba(0,160,255,0.85); }
- .b-ok{ background: rgba(80,255,160,0.85); }
- .b-bad{ background: rgba(255,90,90,0.9); }
- /* Modes */
- .mode-grid{ position:relative; z-index:1; display:grid; gap: 10px; }
- .mode{
- border: 1px solid rgba(80,200,255,0.12);
- background: rgba(6,18,55,0.22);
- border-radius: 12px;
- padding: 10px;
- }
- .mode-h{ color: rgba(220,250,255,0.88); font-weight: 800; font-size: 12px; margin-bottom: 8px; }
- .mode-b{ display:flex; flex-wrap: wrap; gap: 8px; }
- .pill{
- padding: 6px 10px;
- border-radius: 999px;
- border: 1px solid rgba(80,200,255,0.16);
- background: rgba(10,30,80,0.28);
- color: rgba(190,225,255,0.82);
- font-size: 12px;
- font-weight: 700;
- }
- .pill.warn{ border-color: rgba(255,200,60,0.25); background: rgba(255,200,60,0.10); color: rgba(255,230,170,0.9); }
- .pill.mute{ border-color: rgba(160,180,220,0.16); background: rgba(10,30,80,0.12); color: rgba(170,200,240,0.75); }
- /* Alarms */
- .alarm-list{
- flex:1;
- min-height:0;
- overflow:auto;
- position:relative; z-index:1; height: 100%; overflow:hidden; padding-right: 6px; }
- .alarm-item{
- display:flex;
- justify-content:space-between;
- gap: 10px;
- padding: 10px 0;
- border-bottom: 1px solid rgba(80,200,255,0.10);
- }
- .a-title{ display:flex; align-items:center; gap: 10px; }
- .lvl{
- min-width: 38px;
- height: 18px;
- display:inline-flex;
- align-items:center;
- justify-content:center;
- border-radius: 6px;
- font-size: 11px;
- font-weight: 900;
- letter-spacing: 0.5px;
- }
- .lv-1{ background: rgba(255,90,90,0.16); border: 1px solid rgba(255,90,90,0.35); color: rgba(255,170,170,0.95); }
- .lv-2{ background: rgba(255,200,60,0.14); border: 1px solid rgba(255,200,60,0.30); color: rgba(255,235,190,0.92); }
- .lv-3{ background: rgba(0,200,255,0.12); border: 1px solid rgba(0,200,255,0.25); color: rgba(190,245,255,0.92); }
- .lv-4{ background: rgba(160,180,220,0.12); border: 1px solid rgba(160,180,220,0.22); color: rgba(205,225,255,0.85); }
- .txt{ color: rgba(235,255,255,0.92); font-weight: 900; }
- .a-sub{ margin-top: 6px; display:flex; gap: 10px; color: rgba(180,220,255,0.68); font-size: 12px; }
- .a-sub .time{ margin-left:auto; opacity:0.85; }
- .a-actions{ display:flex; align-items:center; gap: 8px; }
- .btn{
- height: 28px;
- padding: 0 10px;
- border-radius: 10px;
- border: 1px solid rgba(80,200,255,0.18);
- background: rgba(10,30,80,0.22);
- color: rgba(200,235,255,0.82);
- cursor:pointer;
- font-weight: 800;
- font-size: 12px;
- }
- .btn.primary{
- background: rgba(0,200,255,0.16);
- color: rgba(235,255,255,0.92);
- }
- .btn.ghost{ background: rgba(10,30,80,0.12); }
- .btn.icon{ height: 30px; border-radius: 10px; }
- .empty{ padding: 18px 0; color: rgba(180,220,255,0.55); text-align:center; }
- /* Map */
- .map-wrap{ flex:1; min-height:0; }
- .map-frame{
- position: relative;
- height:100%;
- border-radius: 16px;
- border: 1px solid rgba(80,200,255,0.18);
- background: linear-gradient(180deg, rgba(8,22,60,0.42), rgba(8,22,60,0.18));
- box-shadow: 0 0 28px rgba(0,160,255,0.08);
- position:relative;
- overflow:hidden;
- }
- .map-tools{
- position:absolute;
- top: 10px;
- left: 12px;
- right: 12px;
- display:flex;
- justify-content:space-between;
- gap: 10px;
- z-index: 3;
- }
- .search{
- display:flex;
- gap: 8px;
- align-items:center;
- }
- .search input{
- width: 180px;
- height: 30px;
- border-radius: 10px;
- border: 1px solid rgba(80,200,255,0.18);
- background: rgba(0,0,0,0.18);
- color: rgba(235,255,255,0.9);
- padding: 0 10px;
- outline:none;
- }
- .select{
- height: 30px;
- min-width: 100px;
- border-radius: 10px;
- border: 1px solid rgba(80,200,255,0.18);
- background: rgba(0,0,0,0.18);
- color: rgba(200,235,255,0.85);
- display:flex;
- align-items:center;
- justify-content:space-between;
- padding: 0 10px;
- }
- .caret{ opacity:0.8; }
- .map-canvas{
- padding-bottom: calc(var(--s) * 120px);
- position:absolute;
- inset:0;
- background-image:
- radial-gradient(900px 520px at 50% 35%, rgba(0,220,255,0.10), rgba(0,0,0,0)),
- radial-gradient(720px 420px at 30% 75%, rgba(30,120,255,0.10), rgba(0,0,0,0));
- background-size: cover;
- background-position:center;
- }
- .roads{
- position:absolute;
- inset:0;
- z-index: 1;
- opacity: 0.9;
- }
- .r{
- fill:none;
- stroke-width: 10;
- stroke-linecap: round;
- filter: drop-shadow(0 0 8px rgba(0,255,255,0.25));
- }
- .r.g{ stroke: rgba(60,255,160,0.82); }
- .r.b{ stroke: rgba(60,190,255,0.88); }
- .r.y{ stroke: rgba(255,220,80,0.88); }
- .r.red{ stroke: rgba(255,90,90,0.92); }
- .node{ fill: rgba(0,220,255,0.7); stroke: rgba(255,255,255,0.25); stroke-width: 2; }
- .popup{
- position:absolute;
- left: 50%;
- top: 62%;
- transform: translate(-50%, -50%);
- width: min(360px, 46vw);
- border-radius: 14px;
- border: 1px solid rgba(80,200,255,0.20);
- background: rgba(0,0,0,0.35);
- backdrop-filter: blur(10px);
- padding: 12px 14px;
- z-index: 2;
- }
- .popup-title{ color: rgba(235,255,255,0.92); font-weight: 900; margin-bottom: 6px; }
- .popup-line{ color: rgba(190,225,255,0.78); font-size: 12px; margin-top: 4px; }
- .legend-box{
- position:absolute;
- right: 14px;
- bottom: calc(var(--s) * 150px);
- width: 140px;
- border-radius: 14px;
- border: 1px solid rgba(80,200,255,0.16);
- background: rgba(0,0,0,0.22);
- backdrop-filter: blur(8px);
- padding: 10px 10px;
- z-index: 2;
- }
- .legend-title{ color: rgba(230,250,255,0.9); font-weight: 900; margin-bottom: 8px; }
- .legend-row{ display:flex; align-items:center; gap: 8px; padding: 4px 0; color: rgba(190,225,255,0.78); font-size: 12px; }
- .chip{ width:10px; height:10px; border-radius: 3px; display:inline-block; }
- .c1{ background: rgba(60,190,255,0.88); }
- .c2{ background: rgba(60,255,160,0.82); }
- .c3{ background: rgba(255,220,80,0.88); }
- .c4{ background: rgba(0,220,255,0.78); }
- .c5{ background: rgba(170,255,240,0.78); }
- .c6{ background: rgba(140,190,255,0.78); }
- .c7{ background: rgba(255,180,120,0.78); }
- .c8{ background: rgba(255,200,60,0.78); }
- .c9{ background: rgba(120,140,180,0.78); }
- .c10{ background: rgba(255,220,80,0.72); }
- .c11{ background: rgba(255,90,90,0.90); }
- /* Tables */
- .tbl-wrap{
- flex:1;
- min-height:0;
- overflow:auto;
- height:100%; overflow:hidden; }
- .tbl{
- width:100%;
- border-collapse: collapse;
- color: rgba(220,250,255,0.86);
- font-size: 12px;
- table-layout: fixed;
- }
- .tbl thead th{
- position:sticky;
- top:0;
- z-index:1;
- background: rgba(8,22,60,0.65);
- color: rgba(180,230,255,0.72);
- font-weight: 900;
- border-bottom: 1px solid rgba(80,200,255,0.12);
- padding: 8px 6px;
- text-align: center;
- box-sizing: border-box;
- }
- .tbl td{
- padding: 8px 6px;
- border-bottom: 1px solid rgba(80,200,255,0.10);
- text-align: center;
- box-sizing: border-box;
- }
- .tbl .name{
- color: rgba(235,255,255,0.92);
- font-weight: 800;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- cursor: default;
- }
- .tbl tbody tr.hl{ background: rgba(0,200,255,0.10); }
- .tag{
- display:inline-flex;
- align-items:center;
- justify-content:center;
- height: 18px;
- min-width: 40px;
- border-radius: 6px;
- font-weight: 900;
- font-size: 11px;
- border: 1px solid rgba(80,200,255,0.14);
- color: rgba(190,245,255,0.9);
- background: rgba(0,200,255,0.10);
- }
- .tag.lv1{ border-color: rgba(0,220,255,0.20); }
- .tag.lv2{ border-color: rgba(255,220,80,0.25); color: rgba(255,240,200,0.92); background: rgba(255,220,80,0.10); }
- .tag.lv3{ border-color: rgba(255,140,140,0.24); color: rgba(255,200,200,0.92); background: rgba(255,90,90,0.10); }
- .tag.lv4{ border-color: rgba(160,180,220,0.18); color: rgba(210,230,255,0.86); background: rgba(160,180,220,0.10); }
- .st{ font-weight: 900; }
- .st.wait{ color: rgba(190,225,255,0.78); }
- .st.run{ color: rgba(255,220,80,0.92); }
- .link{
- color: rgba(130,230,255,0.95);
- text-decoration:none;
- font-weight: 900;
- }
- .tbl.simple thead th{ position:static; background: transparent; }
- /* ====== Bottom nav ====== */
- .bottombar{
- flex: 0 0 auto;
- height: clamp(88px, 11vh, 122px);
- display:flex;
- align-items:center;
- justify-content:center;
- padding: 0 14px 10px;
- }
- .nav-row{
- display:flex;
- align-items:center;
- justify-content:center;
- gap: clamp(10px, 1vw, 16px);
- }
- .bottombar::before{
- content:"";
- position:absolute;
- inset:0;
- background: linear-gradient(0deg, rgba(10,30,80,0.85), rgba(10,30,80,0.20));
- border-top: 1px solid rgba(80,200,255,0.16);
- pointer-events:none;
- }
- .nav-viewport{
- width: min(860px, 78vw);
- overflow:hidden;
- position:relative;
- z-index: 1;
- }
- .nav-track{
- display:flex;
- gap: 12px;
- transition: transform 0.25s ease;
- will-change: transform;
- }
- .nav-item{
- width: clamp(92px, 6.8vw, 124px);
- height: clamp(72px, 9vh, 96px);
- border-radius: 16px;
- border: 1px solid rgba(80,200,255,0.16);
- background: rgba(0,0,0,0.12);
- cursor:pointer;
- display:flex;
- flex-direction:column;
- align-items:center;
- justify-content:center;
- gap: 8px;
- color: rgba(200,235,255,0.76);
- position:relative;
- transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease;
- }
- .nav-item.active{
- background: rgba(0,200,255,0.10);
- color: rgba(235,255,255,0.92);
- box-shadow: 0 0 20px rgba(0,200,255,0.14);
- }
- .nav-item:hover{
- transform: translateZ(0) scale(1.03);
- border-color: rgba(0,220,255,0.26);
- box-shadow: 0 0 20px rgba(0,220,255,0.10);
- }
- .nav-ic{
- width: clamp(58px, 4.8vw, 86px);
- height: clamp(52px, 5.8vh, 78px);
- border-radius: 14px;
- display:flex;
- align-items:center;
- justify-content:center;
- border: 1px solid rgba(80,200,255,0.18);
- background: rgba(10,30,80,0.22);
- }
- .nav-img{
- width: 100%;
- background: center/contain no-repeat;
- filter: drop-shadow(0 0 10px rgba(80,200,255,0.35));
- }
- .nav-ic.glow{
- box-shadow: 0 0 18px rgba(0,220,255,0.22);
- background: rgba(0,200,255,0.10);
- }
- .nav-t{ font-weight: 900; font-size: 12px; }
- .arrow{
- position:relative;
- z-index: 2;
- width: 38px; height: 38px;
- border-radius: 999px;
- border: 1px solid rgba(80,200,255,0.18);
- background: rgba(0,0,0,0.18);
- color: rgba(220,250,255,0.9);
- font-size: 22px;
- line-height: 38px;
- cursor:pointer;
- }
- /* Icons (CSS placeholders) */
- .ic{ width:18px; height:18px; display:inline-block; position:relative; }
- .ic-home::before{ content:""; position:absolute; inset:2px 3px 6px 3px; border:2px solid rgba(190,245,255,0.85); border-bottom:none; transform: skewY(-10deg); }
- .ic-home::after{ content:""; position:absolute; left:5px; right:5px; bottom:6px; height:10px; border:2px solid rgba(190,245,255,0.85); background: rgba(0,200,255,0.06); }
- .ic-eye::before{ content:""; position:absolute; left:1px; right:1px; top:5px; height:10px; border:2px solid rgba(190,245,255,0.85); border-radius: 999px; }
- .ic-eye::after{ content:""; position:absolute; left:7px; top:9px; width:4px; height:4px; border-radius: 999px; background: rgba(190,245,255,0.85); }
- .ic-shield::before{ content:""; position:absolute; left:4px; right:4px; top:2px; bottom:2px; border:2px solid rgba(190,245,255,0.85); border-radius: 8px; clip-path: polygon(0 0, 100% 0, 100% 55%, 50% 100%, 0 55%); }
- .ic-road::before{ content:""; position:absolute; left:7px; top:2px; bottom:2px; width:4px; border-left:2px solid rgba(190,245,255,0.85); border-right:2px solid rgba(190,245,255,0.85); }
- .ic-road::after{ content:""; position:absolute; left:9px; top:4px; bottom:4px; width:0; border-left:2px dashed rgba(190,245,255,0.65); }
- .ic-chart::before{ content:""; position:absolute; left:2px; right:2px; bottom:2px; top:2px; border:2px solid rgba(190,245,255,0.85); border-radius: 6px; }
- .ic-chart::after{ content:""; position:absolute; left:5px; bottom:6px; width:10px; height:6px; border-left:2px solid rgba(0,220,255,0.85); border-bottom:2px solid rgba(0,220,255,0.85); transform: skewX(-15deg); }
- .ic-gear::before{ content:""; position:absolute; inset:3px; border:2px solid rgba(190,245,255,0.85); border-radius: 999px; }
- .ic-gear::after{ content:""; position:absolute; inset:7px; background: rgba(190,245,255,0.85); border-radius: 999px; opacity:0.8; }
- .ic-bell::before{ content:""; position:absolute; left:4px; right:4px; top:3px; bottom:4px; border:2px solid rgba(190,245,255,0.85); border-radius: 10px 10px 8px 8px; }
- .ic-bell::after{ content:""; position:absolute; left:8px; bottom:2px; width:4px; height:4px; border-radius: 999px; background: rgba(190,245,255,0.85); }
- .ic-chip::before{ content:""; position:absolute; inset:3px; border:2px solid rgba(190,245,255,0.85); border-radius: 6px; }
- .ic-chip::after{ content:""; position:absolute; left:6px; right:6px; top:8px; height:0; border-top:2px dashed rgba(190,245,255,0.65); }
- /* Responsive */
- @media (max-width: 1180px){
- .grid{ grid-template-columns: 1fr; }
- .nav-viewport{ width: 94vw; }
- .map-frame{ min-height: 42vh; }
- }
- /* ====== Chart layout align with UI sample ====== */
- .card-a .row{ flex-direction: row-reverse; gap: calc(var(--s) * 14px); }
- .card-a .donut{ width: calc(var(--s) * 160px); height: calc(var(--s) * 160px); }
- .card-a .legend{ flex: 1; padding-right: calc(var(--s) * 6px); }
- .card-b .row-b{ flex-direction: row; gap: calc(var(--s) * 14px); }
- .card-b .donut.small{ width: calc(var(--s) * 150px); height: calc(var(--s) * 150px); }
- .card-r1 .row{ flex-direction: row-reverse; gap: calc(var(--s) * 14px); }
- .card-r1 .donut{ width: calc(var(--s) * 160px); height: calc(var(--s) * 160px); }
- /* ====== Bottom Dock (overlay on map) ====== */
- .dock-wrap{
- position:absolute;
- left:50%;
- bottom: calc(var(--s) * 14px);
- transform: translateX(-50%);
- width: min(86%, calc(var(--s) * 980px)); /* 基本与地图主体宽度一致 */
- display:flex;
- justify-content:center;
- pointer-events:auto;
- z-index: 9;
- }
- .dockbar{
- width: 100%;
- display:flex;
- align-items:flex-end;
- justify-content:space-between;
- gap: calc(var(--s) * 22px);
- padding: calc(var(--s) * 6px) calc(var(--s) * 10px);
- }
- .dock-item{
- width: calc(var(--s) * 98px);
- height: calc(var(--s) * 124px);
- display:flex;
- flex-direction:column;
- align-items:center;
- justify-content:flex-start;
- cursor:pointer;
- transform-origin: 50% 80%;
- transition: transform .08s linear;
- }
- .dock-icon{
- width: calc(var(--s) * 98px);
- height: calc(var(--s) * 86px);
- background: center/contain no-repeat;
- filter: drop-shadow(0 0 calc(var(--s) * 12px) rgba(70, 190, 255, .35));
- }
- .dock-label{
- margin-top: calc(var(--s) * 10px);
- font-size: calc(var(--s) * 14px);
- line-height: 1;
- opacity: .92;
- text-shadow: 0 0 calc(var(--s) * 8px) rgba(0, 160, 255, .25);
- color: rgba(235,255,255,0.92);
- text-shadow: 0 0 calc(var(--s) * 10px) rgba(0,160,255,.35);
- }
- .dock-item:hover .dock-icon{
- filter: drop-shadow(0 0 calc(var(--s) * 18px) rgba(120, 230, 255, .65));
- }
- /* ====== Patch: header corner info (match 示例图) ====== */
- .top-left, .top-right{
- align-self: start;
- padding-top: calc(var(--s) * 60px);
- }
- .weather{
- display:flex;
- align-items:center;
- gap: calc(var(--s) * 10px);
- color: rgba(235,255,255,0.92);
- }
- .dot-sun{
- width: calc(var(--s) * 8px);
- height: calc(var(--s) * 8px);
- border-radius: 50%;
- background: rgba(255,220,120,0.95);
- box-shadow: 0 0 calc(var(--s) * 10px) rgba(255,220,120,0.55);
- display:inline-block;
- }
- .w1{ font-size: calc(var(--s) * 18px); font-weight: 700; letter-spacing: 1px; }
- .w2{ font-size: calc(var(--s) * 16px); opacity: .9; }
- .clock{ text-align:right; color: rgba(235,255,255,0.92); }
- .time{
- font-weight: 900;
- font-size: calc(var(--s) * 28px);
- line-height: 1;
- letter-spacing: 1px;
- text-shadow: 0 0 calc(var(--s) * 10px) rgba(90, 200, 255, 0.28);
- }
- .date{
- margin-top: calc(var(--s) * 6px);
- font-size: calc(var(--s) * 12px);
- color: rgba(190,225,255,0.78);
- }
- /* ====== Patch: side cards sizing (avoid squashed/hidden) ====== */
- .left .card-a, .right .card-a{ flex: 0 0 calc(var(--s) * 220px); }
- .left .card-b{ flex: 0 0 calc(var(--s) * 240px); }
- .right .card-b{ flex: 0 0 calc(var(--s) * 230px); }
- .left .grow, .right .grow{ flex: 1 1 auto; min-height: calc(var(--s) * 230px); }
- /* ====== Patch: dock arrows + label color (match 示例图) ====== */
- .dock-wrap{
- display:flex;
- align-items:center;
- justify-content:center;
- gap: calc(var(--s) * 22px);
- }
- .dock-arrow{
- width: calc(var(--s) * 54px);
- height: calc(var(--s) * 54px);
- background: center/contain no-repeat;
- opacity: .92;
- cursor: pointer;
- transition: transform .18s ease, opacity .18s ease, filter .18s ease;
- filter: drop-shadow(0 0 calc(var(--s) * 10px) rgba(60, 180, 255, .35));
- }
- .dock-arrow:hover{
- opacity: 1;
- transform: translateZ(0) scale(1.08);
- filter: drop-shadow(0 0 calc(var(--s) * 14px) rgba(90, 210, 255, .6));
- }
- .dock-arrow.left{ background-image: url("~@/assets/main/main-left.png"); }
- .dock-arrow.right{ background-image: url("~@/assets/main/main-right.png"); }
- .dock-label{
- color: rgba(235,255,255,0.92) !important;
- text-shadow: 0 0 calc(var(--s) * 8px) rgba(0, 160, 255, 0.28);
- }
- </style>
|