| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446 |
- <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="copyright">东土科技</div>
- <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);
- }
- .top-right {
- position: relative;
- }
- .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);
- }
- /* 版权信息 */
- .copyright {
- display: flex;
- align-items: center;
- font-size: 14px;
- color: #FFF;
- line-height: 30px;
- text-align: center;
- position: absolute;
- right: 160px;
- display: none;
- }
- .copyright::before,
- .copyright::after {
- content: "";
- display: block;
- width: 4px;
- height: 4px;
- border-radius: 2px;
- background: #e7e7e7;
- }
- .copyright::before {
- margin-right: 10px;
- }
- .copyright::after {
- margin-left: 10px;
- }
- </style>
|