|
|
@@ -1,41 +1,148 @@
|
|
|
<template>
|
|
|
<LoginLayout>
|
|
|
-
|
|
|
- <!-- 背景 -->
|
|
|
<template #background>
|
|
|
- <div class="login-bg"></div>
|
|
|
- <!-- 漂浮闪烁点 -->
|
|
|
- <div class="spark-layer" aria-hidden="true">
|
|
|
- <span v-for="d in dots" :key="d.id" class="spark" :style="dotStyle(d)" />
|
|
|
- </div>
|
|
|
+ <video class="bg-video" autoplay muted loop playsinline>
|
|
|
+ <source src="@/assets/main/main-bg.mp4" type="video/mp4" />
|
|
|
+ </video>
|
|
|
</template>
|
|
|
|
|
|
<template #main>
|
|
|
-
|
|
|
- </template>
|
|
|
+ <div class="menu-container">
|
|
|
+
|
|
|
+ <div class="menu-side left-side">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in leftMenus"
|
|
|
+ :key="'left-'+index"
|
|
|
+ class="menu-item"
|
|
|
+ @click="handleMenuClick(item)"
|
|
|
+ >
|
|
|
+ <div class="icon-container">
|
|
|
+ <img :src="item.halo" class="halo-img halo-normal" v-if="item.halo" />
|
|
|
+ <img :src="item.hoverHalo" class="halo-img halo-hover" v-if="item.hoverHalo" />
|
|
|
+
|
|
|
+ <img :src="item.icon" class="icon-img img-normal" v-if="item.icon" />
|
|
|
+ <div class="icon-placeholder img-normal" v-else></div>
|
|
|
+
|
|
|
+ <img :src="item.hoverIcon || item.icon" class="icon-img img-hover" v-if="item.icon" />
|
|
|
+ <div class="icon-placeholder img-hover" v-else></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <span class="menu-text">{{ item.name }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="menu-side right-side">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in rightMenus"
|
|
|
+ :key="'right-'+index"
|
|
|
+ class="menu-item"
|
|
|
+ @click="handleMenuClick(item)"
|
|
|
+ >
|
|
|
+ <div class="icon-container">
|
|
|
+ <img :src="item.halo" class="halo-img halo-normal" v-if="item.halo" />
|
|
|
+ <img :src="item.hoverHalo" class="halo-img halo-hover" v-if="item.hoverHalo" />
|
|
|
+
|
|
|
+ <img :src="item.icon" class="icon-img img-normal" v-if="item.icon" />
|
|
|
+ <div class="icon-placeholder img-normal" v-else></div>
|
|
|
+
|
|
|
+ <img :src="item.hoverIcon || item.icon" class="icon-img img-hover" v-if="item.icon" />
|
|
|
+ <div class="icon-placeholder img-hover" v-else></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <span class="menu-text">{{ item.name }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
</LoginLayout>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import LoginLayout from "@/layouts/LoginLayout.vue";
|
|
|
-import BottomDock from "@/components/ui/BottomDock.vue";
|
|
|
|
|
|
export default {
|
|
|
name: "MainPage",
|
|
|
components: {
|
|
|
LoginLayout,
|
|
|
- BottomDock
|
|
|
- },
|
|
|
- created() {
|
|
|
-
|
|
|
},
|
|
|
data() {
|
|
|
+ // 假设公共的光圈图片路径(按需修改为你实际的文件名)
|
|
|
+ const defaultHalo = require('@/assets/main/main-menu-bg.png');
|
|
|
+ const defaultHoverHalo = require('@/assets/main/main-menu-bg-hover.png');
|
|
|
+
|
|
|
return {
|
|
|
- // 亮点
|
|
|
+ baseW: 1920,
|
|
|
+ baseH: 1080,
|
|
|
+ scale: 1,
|
|
|
dots: [],
|
|
|
+ // 菜单配置数据
|
|
|
+ menuList: [
|
|
|
+ {
|
|
|
+ name: '首页',
|
|
|
+ icon: require('@/assets/main/main-home.png'),
|
|
|
+ hoverIcon: require('@/assets/main/main-home-hover.png'),
|
|
|
+ halo: defaultHalo, // 引入光圈图片
|
|
|
+ hoverHalo: defaultHoverHalo, // 引入高亮光圈图片
|
|
|
+ side: 'left',
|
|
|
+ path: '/home'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '状态监测',
|
|
|
+ icon: require('@/assets/main/main-surve.png'),
|
|
|
+ hoverIcon: require('@/assets/main/main-surve-hover.png'),
|
|
|
+ halo: defaultHalo,
|
|
|
+ hoverHalo: defaultHoverHalo,
|
|
|
+ side: 'left',
|
|
|
+ path: '/surve'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '勤务管理',
|
|
|
+ icon: require('@/assets/main/main-security.png'),
|
|
|
+ hoverIcon: require('@/assets/main/main-security-hover.png'),
|
|
|
+ halo: defaultHalo,
|
|
|
+ hoverHalo: defaultHoverHalo,
|
|
|
+ side: 'left',
|
|
|
+ path: '/security'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '干线协调',
|
|
|
+ icon: require('@/assets/main/main-coor.png'),
|
|
|
+ hoverIcon: require('@/assets/main/main-coor-hover.png'),
|
|
|
+ halo: defaultHalo,
|
|
|
+ hoverHalo: defaultHoverHalo,
|
|
|
+ side: 'right',
|
|
|
+ path: '/trunk'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '数据分析',
|
|
|
+ icon: require('@/assets/main/main-watch.png'),
|
|
|
+ hoverIcon: require('@/assets/main/main-watch-hover.png'),
|
|
|
+ halo: defaultHalo,
|
|
|
+ hoverHalo: defaultHoverHalo,
|
|
|
+ side: 'right',
|
|
|
+ path: '/watch'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '系统设置',
|
|
|
+ icon: require('@/assets/main/main-setting.png'),
|
|
|
+ hoverIcon: require('@/assets/main/main-setting-hover.png'),
|
|
|
+ halo: defaultHalo,
|
|
|
+ hoverHalo: defaultHoverHalo,
|
|
|
+ side: 'right',
|
|
|
+ path: '/setting'
|
|
|
+ },
|
|
|
+ ]
|
|
|
};
|
|
|
},
|
|
|
+ computed: {
|
|
|
+ leftMenus() {
|
|
|
+ return this.menuList.filter(item => item.side === 'left');
|
|
|
+ },
|
|
|
+ rightMenus() {
|
|
|
+ return this.menuList.filter(item => item.side === 'right');
|
|
|
+ }
|
|
|
+ },
|
|
|
mounted() {
|
|
|
this.updateScale();
|
|
|
window.addEventListener("resize", this.updateScale, { passive: true });
|
|
|
@@ -48,24 +155,21 @@ export default {
|
|
|
updateScale() {
|
|
|
const w = window.innerWidth || this.baseW;
|
|
|
const h = window.innerHeight || this.baseH;
|
|
|
- // 取 min 保证不裁切(大屏常见)
|
|
|
this.scale = Math.min(w / this.baseW, h / this.baseH);
|
|
|
- // 写到 css 变量,方便 calc(var(--s) * xxxpx)
|
|
|
this.$el && this.$el.style.setProperty("--s", this.scale.toFixed(6));
|
|
|
},
|
|
|
- // ---------- 闪烁点 ----------
|
|
|
initDots() {
|
|
|
const count = 50;
|
|
|
const arr = [];
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
arr.push({
|
|
|
id: i,
|
|
|
- x: 18 + Math.random() * 64, // 百分比布局,适配任意屏幕
|
|
|
+ x: 18 + Math.random() * 64,
|
|
|
y: 22 + Math.random() * 56,
|
|
|
- r: 1.2 + Math.random() * 2.8, // 半径
|
|
|
+ r: 1.2 + Math.random() * 2.8,
|
|
|
a: 0.45 + Math.random() * 0.55,
|
|
|
- d: Math.random() * 2.8, // delay
|
|
|
- t: 1.8 + Math.random() * 2.6, // duration
|
|
|
+ d: Math.random() * 2.8,
|
|
|
+ t: 1.8 + Math.random() * 2.6,
|
|
|
});
|
|
|
}
|
|
|
this.dots = arr;
|
|
|
@@ -81,78 +185,170 @@ export default {
|
|
|
animationDuration: `${d.t}s`,
|
|
|
};
|
|
|
},
|
|
|
+ handleMenuClick(item) {
|
|
|
+ if (!item.path) return;
|
|
|
+ this.$router.push(item.path).catch(err => {
|
|
|
+ if (err.name !== 'NavigationDuplicated') {
|
|
|
+ console.error('路由跳转失败:', err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.dock-style {
|
|
|
+/* ================= 全局与背景层 ================= */
|
|
|
+.bg-video {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ z-index: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* ================= 菜单整体布局 ================= */
|
|
|
+.menu-container {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
left: 0;
|
|
|
- bottom: unset !important;
|
|
|
+ width: 100%;
|
|
|
height: 100%;
|
|
|
- margin: auto 0;
|
|
|
- border-radius: calc(var(--s) * 1.125rem);
|
|
|
- background: linear-gradient(180deg, rgba(10, 35, 70, .18), rgba(0, 0, 0, .08));
|
|
|
- box-shadow: 0 0 calc(var(--s) * 1.625rem) rgba(0, 140, 255, .08) inset;
|
|
|
- backdrop-filter: blur(.125rem);
|
|
|
+ pointer-events: none;
|
|
|
+ z-index: 10;
|
|
|
}
|
|
|
-.dock-style ::v-deep .right-arrow {
|
|
|
- margin-left: 100px;
|
|
|
+
|
|
|
+.menu-side {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: calc(var(--s) * 70px);
|
|
|
+ pointer-events: auto;
|
|
|
}
|
|
|
-.dock-style ::v-deep .left-arrow {
|
|
|
- margin-right: 100px;
|
|
|
+
|
|
|
+.left-side { left: calc(var(--s) * 180px); }
|
|
|
+.right-side { right: calc(var(--s) * 180px); }
|
|
|
+
|
|
|
+/* ================= 单个菜单项 ================= */
|
|
|
+.menu-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ cursor: pointer;
|
|
|
}
|
|
|
-.main-page {
|
|
|
+
|
|
|
+/* --- 图标复合容器 --- */
|
|
|
+.icon-container {
|
|
|
position: relative;
|
|
|
- width: 100vw;
|
|
|
- height: 100vh;
|
|
|
- overflow: hidden;
|
|
|
- --s: 1;
|
|
|
- color: #d9efff;
|
|
|
- user-select: none;
|
|
|
+ width: calc(var(--s) * 130px);
|
|
|
+ height: calc(var(--s) * 100px);
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: calc(var(--s) * 12px);
|
|
|
}
|
|
|
|
|
|
-.login-bg {
|
|
|
- background: url('@/assets/images/main-background.png') no-repeat center/cover;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- filter: saturate(1.05) contrast(1.03);
|
|
|
+/* --- 1. 光圈底座样式 (图片) --- */
|
|
|
+.halo-img {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0; /* 根据你切图的空白区域自行微调,比如 calc(var(--s) * 5px) */
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 90%; /* 根据切图实际比例微调大小 */
|
|
|
+ height: auto;
|
|
|
+ object-fit: contain;
|
|
|
+ transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
|
+ z-index: 1;
|
|
|
}
|
|
|
|
|
|
-/* 闪烁亮点 */
|
|
|
-.spark-layer {
|
|
|
+/* 光圈默认展示 normal,隐藏 hover */
|
|
|
+.halo-normal { opacity: 1; }
|
|
|
+.halo-hover { opacity: 0; }
|
|
|
+
|
|
|
+/* --- 2. 顶部图标样式 (图片) --- */
|
|
|
+.icon-img {
|
|
|
position: absolute;
|
|
|
- inset: 0;
|
|
|
- z-index: 4;
|
|
|
- pointer-events: none;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
+ bottom: calc(var(--s) * 50px); /* 浮在光圈上方,根据切图微调 */
|
|
|
+ width: 80%;
|
|
|
+ height: 80%;
|
|
|
+ object-fit: contain;
|
|
|
+ transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
|
+ z-index: 2;
|
|
|
}
|
|
|
|
|
|
-.spark {
|
|
|
+.icon-placeholder {
|
|
|
position: absolute;
|
|
|
- border-radius: 999px;
|
|
|
- background: radial-gradient(circle, rgba(140, 235, 255, .95), rgba(140, 235, 255, 0) 70%);
|
|
|
- animation-name: twinkle;
|
|
|
- animation-timing-function: ease-in-out;
|
|
|
- animation-iteration-count: infinite;
|
|
|
+ bottom: calc(var(--s) * 50px);
|
|
|
+ width: 40%;
|
|
|
+ height: 40%;
|
|
|
+ border-radius: 10px;
|
|
|
+ transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
|
+ z-index: 2;
|
|
|
}
|
|
|
|
|
|
-@keyframes twinkle {
|
|
|
- 0% {
|
|
|
- transform: translateZ(0) scale(.7);
|
|
|
- opacity: .22;
|
|
|
- }
|
|
|
+/* 图标默认展示 normal,隐藏 hover */
|
|
|
+.img-normal { opacity: 1; transform: translateY(0); }
|
|
|
+.img-hover { opacity: 0; transform: translateY(0); }
|
|
|
|
|
|
- 50% {
|
|
|
- transform: translateZ(0) scale(1.25);
|
|
|
- opacity: .9;
|
|
|
- }
|
|
|
+/* ================= Hover 联动动效 ================= */
|
|
|
|
|
|
- 100% {
|
|
|
- transform: translateZ(0) scale(.7);
|
|
|
- opacity: .22;
|
|
|
- }
|
|
|
+/* A. 光圈交叉淡入淡出,并可加入轻微放大效果增强动感 */
|
|
|
+.menu-item:hover .halo-normal {
|
|
|
+ opacity: 0;
|
|
|
+}
|
|
|
+.menu-item:hover .halo-hover {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateX(-50%) scale(1.08); /* 高亮时底座稍微扩散放大 */
|
|
|
+}
|
|
|
+
|
|
|
+/* B. 顶部图标交叉淡入淡出,并上浮 */
|
|
|
+.menu-item:hover .img-normal {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(calc(var(--s) * -8px));
|
|
|
+}
|
|
|
+.menu-item:hover .img-hover {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(calc(var(--s) * -8px));
|
|
|
+}
|
|
|
+
|
|
|
+/* C. 文字点亮并微放 */
|
|
|
+.menu-text {
|
|
|
+ color: #a6c4eb;
|
|
|
+ font-size: calc(var(--s) * 18px);
|
|
|
+ font-weight: 500;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+.menu-item:hover .menu-text {
|
|
|
+ color: #ffffff;
|
|
|
+ transform: scale(1.05);
|
|
|
+}
|
|
|
+
|
|
|
+/* ================= 菜单弧度调整 (贴合地球) ================= */
|
|
|
+
|
|
|
+/* 左侧菜单:第1个(顶部)和第3个(底部)向右(向中间)偏移,形成 ) 弧度 */
|
|
|
+.left-side .menu-item:nth-child(1),
|
|
|
+.left-side .menu-item:nth-child(3) {
|
|
|
+ transform: translateX(calc(var(--s) * 70px)); /* 数值越大,弧度越深 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 左侧菜单:第2个(中间)保持最靠外,如果需要可微调 */
|
|
|
+.left-side .menu-item:nth-child(2) {
|
|
|
+ transform: translateX(0);
|
|
|
+}
|
|
|
+
|
|
|
+/* 右侧菜单:第1个(顶部)和第3个(底部)向左(向中间)偏移,形成 ( 弧度 */
|
|
|
+.right-side .menu-item:nth-child(1),
|
|
|
+.right-side .menu-item:nth-child(3) {
|
|
|
+ transform: translateX(calc(var(--s) * -70px)); /* 数值越大,弧度越深 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 右侧菜单:第2个(中间)保持最靠外 */
|
|
|
+.right-side .menu-item:nth-child(2) {
|
|
|
+ transform: translateX(0);
|
|
|
}
|
|
|
-</style>
|
|
|
+</style>
|