Browse Source

修改BottomDock组件可以关闭自动隐藏,可以自定义样式;修改Main页面使用LoginLayout布局,菜单统一使用BottomDock组件;

画安 1 month ago
parent
commit
584cb3a592
3 changed files with 134 additions and 351 deletions
  1. BIN
      src/assets/images/main-background.png
  2. 64 73
      src/components/ui/BottomDock.vue
  3. 70 278
      src/views/Main.vue

BIN
src/assets/images/main-background.png


+ 64 - 73
src/components/ui/BottomDock.vue

@@ -1,5 +1,11 @@
 <template>
-    <div class="dock-wrapper">
+    <div class="dock-wrapper" 
+         :class="[
+            autoHide ? 'is-auto-hide' : 'is-always-show', 
+            customClass
+         ]"
+         :style="dockStyles">
+         
         <div class="nav-arrow left-arrow" :class="{ 'is-disabled': !canScrollLeft, 'is-active': canScrollLeft }"
             @click="scrollList(-1)">
             <img v-if="canScrollLeft" src="@/assets/main/main-right.png" class="arrow-img left-facing" />
@@ -37,14 +43,35 @@
 <script>
 export default {
     name: 'BottomDock',
+    props: {
+        // 是否自动隐藏 (默认 true: 悬浮升起; false: 常驻显示)
+        autoHide: {
+            type: Boolean,
+            default: true
+        },
+        // 距离屏幕底部的偏移量 (单位 px,正数代表往上抬高)
+        bottomOffset: {
+            type: Number,
+            default: 0
+        },
+        // 允许外部传入的自定义容器样式 (如背景色、宽度等)
+        customStyle: {
+            type: Object,
+            default: () => ({})
+        },
+        // 允许外部传入自定义 class (支持字符串、数组、对象)
+        customClass: {
+            type: [String, Array, Object],
+            default: ''
+        }
+    },
     data() {
         return {
-            activeIndex: 0,
+            activeIndex: -1,
             hoverIndex: null,
-            canScrollLeft: false, // 初始默认在最左侧,所以左侧不能滚
-            canScrollRight: true, // 初始默认右侧有内容,可以滚
+            canScrollLeft: false, 
+            canScrollRight: true, 
             dockItems: [
-                /* ... 你的菜单数据 ... */
                 {
                     label: '首页',
                     imgUrl: require('@/assets/main/main-home.png'),
@@ -87,7 +114,6 @@ export default {
                     route: '/setting',
                     theme: 'blue',
                 },
-                // (建议多加几个测试数据,撑爆容器宽度,才能看到滚动和状态切换)
                 {
                     label: '测试1',
                     imgUrl: require('@/assets/main/main-home.png'),
@@ -106,6 +132,18 @@ export default {
             ]
         };
     },
+    computed: {
+        // 动态计算 CSS 变量和合并自定义样式
+        dockStyles() {
+            // 隐藏状态下,让容器往下沉 (自身高度 160 减去留出给用户触发的边缘高度)
+            // 如果你想让它隐藏得更深,可以把 140 改成 150 甚至 160
+            return {
+                '--dock-show-bottom': `${this.bottomOffset}px`,
+                '--dock-hide-bottom': `${this.bottomOffset - 140}px`,
+                ...this.customStyle 
+            };
+        }
+    },
     watch: {
         $route() {
             this.updateActiveIndexByRoute();
@@ -115,29 +153,21 @@ export default {
         this.updateActiveIndexByRoute();
     },
     mounted() {
-        // 组件挂载后,等 DOM 渲染完毕,立即计算一次初始状态
         this.$nextTick(() => {
             this.checkScrollState();
-            // 监听窗口大小变化,防止屏幕拉伸导致容器尺寸变化
             window.addEventListener('resize', this.checkScrollState);
         });
     },
     beforeDestroy() {
-        // 销毁时移除监听
         window.removeEventListener('resize', this.checkScrollState);
     },
     methods: {
         updateActiveIndexByRoute() {
             const currentPath = this.$route.path;
-            console.log(currentPath)
-            // 查找当前路由路径匹配的菜单项索引
             const matchIndex = this.dockItems.findIndex(item => {
-                // 精确匹配:item.route === currentPath
-                // 模糊匹配(推荐):当前路径包含菜单的路由(解决含有子路由 /monitor/detail 时底部也高亮的问题)
                 return item.route && currentPath.startsWith(item.route);
             });
 
-            // 如果找到了对应的菜单,就更新 activeIndex
             if (matchIndex !== -1) {
                 this.activeIndex = matchIndex;
             }
@@ -146,9 +176,7 @@ export default {
             if (this.activeIndex === index) return;
             this.activeIndex = index;
 
-            // 【新增路由跳转逻辑】
             if (item.route) {
-                // 使用 catch 拦截重复点击同一个路由时的 NavigationDuplicated 报错
                 this.$router.push(item.route).catch(err => {
                     if (err.name !== 'NavigationDuplicated') {
                         console.error('路由跳转失败:', err);
@@ -158,36 +186,25 @@ export default {
 
             this.$emit('change', item);
         },
-
-        // 【新增核心方法】:检查并更新滚动状态
         checkScrollState() {
             const container = this.$refs.listContainer;
             if (!container) return;
 
-            // 1. 如果 scrollLeft 大于 0,说明不在最左边,左侧按钮可点击 (状态1)
             this.canScrollLeft = container.scrollLeft > 0;
-
-            // 2. 如果 scrollLeft + 可视宽度 < 实际总宽度,说明右侧还没滚到底,右侧按钮可点击 (状态1)
-            // 使用 Math.ceil 是为了防止部分高分屏下出现小数像素导致的精度误差
             this.canScrollRight = Math.ceil(container.scrollLeft + container.clientWidth) < container.scrollWidth;
         },
-
         scrollList(direction) {
-            // 如果处于不可滚动状态(状态2),直接 return,不执行滚动
             if (direction === -1 && !this.canScrollLeft) return;
             if (direction === 1 && !this.canScrollRight) return;
 
             const container = this.$refs.listContainer;
-            // 1 个图标宽度 100px + 右侧间距 30px = 130px
-            const scrollAmount = 130; // 调整每次滑动的距离
+            const scrollAmount = 130; 
 
             if (container) {
                 container.scrollBy({
                     left: direction * scrollAmount,
                     behavior: 'smooth'
                 });
-                // 这里的平滑滚动会自动触发上面绑定的 @scroll 事件,
-                // 从而实时调用 checkScrollState 更新按钮状态
             }
         }
     }
@@ -195,37 +212,31 @@ export default {
 </script>
 
 <style scoped>
-/* ================= 整体容器布局 (修复垂直排列的核心) ================= */
+/* ================= 整体容器布局 ================= */
 .dock-wrapper {
     display: flex !important;
     flex-direction: row !important;
-    /* 1. 强制左右水平排列 */
     align-items: center;
     justify-content: center;
     width: 100%;
     height: 160px;
-    /* position: relative; */
-
     position: fixed; 
     left: 0;
     z-index: 9999;
-    
-    /* 🌟 核心修改 1:不用 transform,直接把 bottom 设为负数藏到底部外 */
-    bottom: -140px;
-    /* 🌟 核心修改 2:过渡动画改为监听 bottom 属性 */
     transition: bottom 1.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
     background: linear-gradient(to top, rgba(0, 20, 30, 0.8) 0%, rgba(0, 20, 30, 0.01) 100%);
-
     pointer-events: auto !important;
 }
 
-/* 🌟 核心修改 3:鼠标悬浮时,bottom 归零,完美升起 */
-.dock-wrapper:hover {
-    bottom: 0;
+/* === 状态 A:自动隐藏 (使用 CSS 变量控制) === */
+.dock-wrapper.is-auto-hide {
+    bottom: var(--dock-hide-bottom);
+}
+.dock-wrapper.is-auto-hide:hover {
+    bottom: var(--dock-show-bottom);
 }
 
-/* === 顶部的发光提示线(保持不变,加了一个穿透属性防误触) === */
-.dock-wrapper::before {
+.dock-wrapper.is-auto-hide::before {
     content: '';
     position: absolute;
     top: 0;
@@ -237,26 +248,28 @@ export default {
     box-shadow: 0 0 10px rgba(0, 229, 255, 0.8);
     border-radius: 4px;
     transition: opacity 0.3s;
-    pointer-events: none; /* 防止这根线挡住鼠标划入的判定 */
+    pointer-events: none; 
 }
 
-/* 展开的时候让提示线消失 */
-.dock-wrapper:hover::before {
+.dock-wrapper.is-auto-hide:hover::before {
     opacity: 0;
 }
 
-/* 视窗容器:定宽,超出部分隐藏并允许横向滚动 
-    【精算容器宽度】:
-    要想完美不漏边,容器宽度必须等于:(显示个数 * 元素宽度) + ((显示个数 - 1) * 间距)
-    假设你想完美显示 6 个:(6 * 100px) + (5 * 30px) = 750px
-  */
+/* === 状态 B:常驻显示 (使用 CSS 变量控制) === */
+.dock-wrapper.is-always-show {
+    bottom: var(--dock-show-bottom);
+}
+.dock-wrapper.is-always-show::before {
+    display: none;
+}
+
+/* ================= 内部列表与滚动容器 ================= */
 .dock-list-container {
     width: 750px;
     height: 160px;
     overflow-x: auto;
     overflow-y: hidden;
     white-space: nowrap;
-    /* 2. 强制内部文本/元素绝对不换行 */
     -ms-overflow-style: none;
     scrollbar-width: none;
 }
@@ -265,26 +278,19 @@ export default {
     display: none;
 }
 
-/* 真实的菜单列表:尺寸由内容撑开 */
 .dock-list {
     display: flex !important;
     flex-direction: row !important;
-    /* 3. 菜单项强制水平排列 */
     flex-wrap: nowrap !important;
-    /* 4. 绝对不允许换行 */
     align-items: flex-end;
-    /* 底部对齐 */
     height: 100%;
     width: max-content;
-    /* 5. 关键:真实宽度由内部项决定,从而完美触发父级滚动 */
     min-width: 100%;
     padding-bottom: 20px;
     gap: 30px;
-    /* 图标之间的横向间距 */
 }
 
 /* ================= 左右控制箭头 ================= */
-/* 容器基础样式 */
 .nav-arrow {
     flex-shrink: 0;
     width: 40px;
@@ -294,7 +300,6 @@ export default {
     align-items: center;
     transition: all 0.3s;
     margin: 0 15px;
-    /* 【修改】:去掉之前的底色和边框,因为你现在用整张切图了 */
     background: transparent !important;
     border: none !important;
 }
@@ -303,15 +308,12 @@ export default {
   width: 100%;
   height: 100%;
   object-fit: contain;
-  /* transition: transform 0.2s ease, filter 0.3s ease; */
 }
 
-/* 强制让左侧的图片掉头指向上一个 */
 .left-facing {
   transform: rotate(180deg);
 }
 
-/* 状态2:允许切换时的悬浮放大效果 */
 .nav-arrow.is-active {
   cursor: pointer;
 }
@@ -320,12 +322,10 @@ export default {
   filter: drop-shadow(0 0 10px rgba(0, 229, 255, 0.8));
 }
 
-/* 【关键修复】:由于左箭头自带 rotate(180deg),悬浮放大时不能丢掉它的旋转角度,否则它会瞬间掉头! */
 .nav-arrow.left-arrow.is-active:hover .arrow-img.left-facing {
   transform: rotate(180deg) scale(1.1);
 }
 
-/* 状态1:不允许切换时稍微变暗 */
 .nav-arrow.is-disabled {
   cursor: not-allowed;
   opacity: 0.6; 
@@ -345,7 +345,6 @@ export default {
     transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
 }
 
-/* --- 图标部分 --- */
 .item-icon {
     font-size: 32px;
     color: #a0cfff;
@@ -354,7 +353,6 @@ export default {
     transition: all 0.3s;
 }
 
-/* --- 文字部分 --- */
 .item-label {
     color: #c0c4cc;
     font-size: 14px;
@@ -364,7 +362,6 @@ export default {
 }
 
 /* ================= 交互状态:悬浮与选中 ================= */
-
 .dock-item:hover {
     transform: translateY(-15px) scale(1.15);
 }
@@ -398,24 +395,18 @@ export default {
     text-shadow: 0 0 8px #ffaa00;
 }
 
-/* --- 图标部分 --- */
 .item-icon {
     z-index: 3;
     margin-bottom: 5px;
-    /* 确保图片也能有悬浮时的丝滑过渡 */
     transition: transform 0.3s ease, filter 0.3s ease;
 }
 
-/* 专门针对图片的样式 */
 .custom-icon {
     width: 88px;
-    /* 根据你切图的实际大小调整 */
     height: 64px;
     object-fit: contain;
-    /* 保证图片不变形 */
 }
 
-/* 悬浮时,如果想让图片也发光,可以使用 CSS 的 drop-shadow 滤镜 */
 .dock-item:hover .custom-icon,
 .dock-item.is-active .custom-icon {
     filter: drop-shadow(0 0 8px rgba(0, 229, 255, 0.8));

+ 70 - 278
src/views/Main.vue

@@ -1,74 +1,37 @@
 <template>
-  <div class="main-page" @mousemove="onMouseMove" @mouseleave="onMouseLeave">
-    <!-- 背景 -->
-    <div class="bg"></div>
-
-    <!-- 顶部 Header -->
-    <div class="header">
-      <!-- <div class="title">交通信号控制平台</div> -->
-    </div>
-
-    <!-- 漂浮闪烁点 -->
-    <div class="spark-layer" aria-hidden="true">
-      <span
-        v-for="d in dots"
-        :key="d.id"
-        class="spark"
-        :style="dotStyle(d)"
-      />
-    </div>
-
-    <!-- 中央功能区 -->
-    <div class="center-wrap">
-      <div class="nav-arrow left" @click="onPrev" title="上一页"></div>
+  <LoginLayout>
 
-      <div class="dock">
-        <div
-          v-for="(item, idx) in items"
-          :key="item.key"
-          class="dock-item"
-          :style="itemStyle(idx)"
-          @click="go(item)"
-          @mouseenter="handleHover(idx)"
-          @mouseleave="currentHoverIndex = null"
-        >
-          <div class="icon" :style="iconStyle(idx, item)"></div>
-          <div class="label">{{ item.label }}</div>
-        </div>
+    <!-- 背景 -->
+    <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>
+    </template>
 
-      <div class="nav-arrow right" @click="onNext" title="下一页"></div>
-    </div>
+    <template #main>
+      <BottomDock :auto-hide="false" :custom-class="['dock-style']"></BottomDock>
+    </template>
 
-    <div class="copyright">北京东土正创科技有限公司</div>
-  </div>
+  </LoginLayout>
 </template>
 
 <script>
+import LoginLayout from "@/layouts/LoginLayout.vue";
+import BottomDock from "@/components/ui/BottomDock.vue";
+
 export default {
-  name: "Main",
+  name: "MainPage",
+  components: { 
+    LoginLayout, 
+    BottomDock 
+  },
+  created() {
+
+  },
   data() {
     return {
-      // 以 1920x1080 为设计基准,用 scale 把“设计像素”映射到任意大屏
-      baseW: 1920,
-      baseH: 1080,
-      scale: 1,
-      currentHoverIndex: null,
-
-      // mac dock 效果:鼠标点位
-      mouseX: null,
-      mouseY: null,
-
-            // 6 个功能(route 先对齐到你当前路由:目前只有 /home 可落地,其它模块先通过 query 标记,避免 404)
-      items: [
-        { key: "home", label: "首页", img: "main-home", route: { path: "/home" } },
-        { key: "surve", label: "状态监控", img: "main-surve", route: { path: "/surve", query: { panel: "surve" } } },
-        { key: "security", label: "特勤安保", img: "main-security", route: { path: "/security", query: { panel: "security" } } },
-        { key: "coor", label: "干线协调", img: "main-coor", route: { path: "/coor", query: { panel: "coor" } } },
-        { key: "watch", label: "数据分析", img: "main-watch", route: { path: "/watch", query: { panel: "watch" } } },
-        { key: "setting", label: "系统设置", img: "main-setting", route: { path: "/setting", query: { panel: "setting" } } },
-      ],
-
       // 亮点
       dots: [],
     };
@@ -90,73 +53,6 @@ export default {
       // 写到 css 变量,方便 calc(var(--s) * xxxpx)
       this.$el && this.$el.style.setProperty("--s", this.scale.toFixed(6));
     },
-
-    // ---------- Dock 缩放 ----------
-    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;
-    },
-    itemStyle(idx) {
-      if (this.mouseX == null) return { transform: "translateZ(0) scale(1)" };
-
-      const dock = this.$el.querySelector(".dock");
-      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);
-
-      // 影响半径:约 220 设计像素(随 scale 变化)
-      const R = 220 * this.scale;
-      const t = Math.max(0, 1 - dist / R); // 0~1
-      const s = 1 + 0.35 * (t * t); // 最大放大 1.35
-
-      return { transform: `translateZ(0) scale(${s.toFixed(4)})` };
-    },
-
-    // ---------- 资源映射 ----------
-    assetUrl(file) {
-      try {
-        return require("@/assets/main/" + file);
-      } catch (e) {
-        return "";
-      }
-    },
-    iconStyle(index, item) {
-      return {
-        backgroundImage: this.currentHoverIndex === index ? `url(${this.assetUrl(item.img + '-hover.png')})` : `url(${this.assetUrl(item.img + '.png')})`
-      };
-    },
-
-    handleHover(index) {
-      this.currentHoverIndex = index;
-    },
-
-    // ---------- 导航 ----------
-        go(item) {
-      // 路由对齐:你的路由表里目前只有 /home(其它页面后续再补路由)
-      // 先用 query.panel 区分模块,保证点击不会 404
-      if (this.$router && item.route) this.$router.push(item.route);
-    }
-    ,
-    onPrev() {
-      this.$emit("prev");
-    },
-    onNext() {
-      this.$emit("next");
-    },
-
     // ---------- 闪烁点 ----------
     initDots() {
       const count = 28;
@@ -178,19 +74,36 @@ export default {
       return {
         left: d.x + "%",
         top: d.y + "%",
-        width: `calc(var(--s) * ${d.r * 4}px)`,
-        height: `calc(var(--s) * ${d.r * 4}px)`,
+        width: `${d.r * 4}px`,
+        height: `${d.r * 4}px`,
         opacity: d.a,
         animationDelay: `${d.d}s`,
         animationDuration: `${d.t}s`,
       };
     },
-  },
-};
+  }
+
+}
 </script>
 
 <style scoped>
-.main-page{
+.dock-style {
+  left: 0;
+  bottom: unset !important;
+  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);
+}
+.dock-style ::v-deep .right-arrow {
+  margin-left: 100px;
+}
+.dock-style ::v-deep .left-arrow {
+  margin-right: 100px;
+}
+.main-page {
   position: relative;
   width: 100vw;
   height: 100vh;
@@ -200,167 +113,46 @@ export default {
   user-select: none;
 }
 
-/* 背景图(图2) */
-.bg{
-  position:absolute;
-  inset:0;
-  background: center/cover no-repeat;
-  background-image: url("~@/assets/main/main-bg.png");
+.login-bg {
+  background: url('@/assets/images/main-background.png') no-repeat center/cover;
+  width: 100%;
+  height: 100%;
   filter: saturate(1.05) contrast(1.03);
 }
 
-/* 顶部 header(高度 100 设计像素) */
-.header{
-  position:absolute;
-  left:0;
-  top:0;
-  width:100%;
-  height: calc(var(--s) * 100px);
-  background: center/100% 100% no-repeat;
-  background-image: url("~@/assets/main/main-header.png");
-  display:flex;
-  align-items:center;
-  justify-content:center;
-  z-index: 5;
-}
-.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;
-}
-
-/* 中间区域:使用居中 + 轻微上移(避免写死 1920 的绝对 px) */
-.center-wrap{
-  position:absolute;
-  left:50%;
-  top:46%;
-  transform: translate(-50%, -50%);
-  width: min(92vw, calc(var(--s) * 1600px));
-  display:flex;
-  align-items:center;
-  justify-content:center;
-  gap: calc(var(--s) * 40px);
-  z-index: 6;
-}
-
-/* 左右箭头(54*54) */
-.nav-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));
-}
-.nav-arrow:hover{
-  opacity: 1;
-  transform: translateZ(0) scale(1.08);
-  filter: drop-shadow(0 0 calc(var(--s) * 14px) rgba(90, 210, 255, .6));
-}
-.nav-arrow.left{ background-image: url("~@/assets/main/main-left.png"); }
-.nav-arrow.right{ background-image: url("~@/assets/main/main-right.png"); }
-
-/* 功能按钮 Dock */
-.dock{
-  display:flex;
-  align-items:flex-start;
-  justify-content:center;
-  gap: calc(var(--s) * 45px);
-  padding: calc(var(--s) * 12px) calc(var(--s) * 16px);
-  border-radius: calc(var(--s) * 18px);
-  background: linear-gradient(180deg, rgba(10,35,70,.18), rgba(0,0,0,.08));
-  box-shadow: 0 0 calc(var(--s) * 26px) rgba(0, 140, 255, .08) inset;
-  backdrop-filter: blur(2px);
-  margin: 0 6%;
-}
-
-.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;
-}
-
-.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));
-}
-
-.label{
-  margin-top: calc(var(--s) * 10px);
-  font-size: calc(var(--s) * 16px);
-  line-height: 1;
-  opacity: .92;
-  text-shadow: 0 0 calc(var(--s) * 8px) rgba(0, 160, 255, .25);
-}
-
-/* hover 细节:轻微发光 + label 提亮 */
-.dock-item:hover .icon{
-  filter: drop-shadow(0 0 calc(var(--s) * 18px) rgba(120, 230, 255, .65));
-}
-.dock-item:hover .label{
-  opacity: 1;
-}
-
 /* 闪烁亮点 */
-.spark-layer{
-  position:absolute;
-  inset:0;
+.spark-layer {
+  position: absolute;
+  inset: 0;
   z-index: 4;
   pointer-events: none;
+  width: 100%;
+  height: 100%;
 }
-.spark{
-  position:absolute;
+
+.spark {
+  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;
 }
-@keyframes twinkle{
-  0%{ transform: translateZ(0) scale(.7); opacity: .22; }
-  50%{ transform: translateZ(0) scale(1.25); opacity: .9; }
-  100%{ transform: translateZ(0) scale(.7); opacity: .22; }
-}
-
-/* 版权信息 */
-.copyright {
-  display: flex;
-  align-items: center;
-  font-size: 14px;
-  color: #FFF;
-  line-height: 30px;
-  text-align: center;
-  position: absolute;
-  bottom: 20px;
-  left: calc(50% - 35px);
-  display: none;
-}
 
-.copyright::before,
-.copyright::after {
-  content: "";
-  display: block;
-  width: 4px;
-  height: 4px;
-  border-radius: 2px;
-  background: #e7e7e7;
-}
+@keyframes twinkle {
+  0% {
+    transform: translateZ(0) scale(.7);
+    opacity: .22;
+  }
 
-.copyright::before {
-  margin-right: 10px;
-}
+  50% {
+    transform: translateZ(0) scale(1.25);
+    opacity: .9;
+  }
 
-.copyright::after {
-  margin-left: 10px;
+  100% {
+    transform: translateZ(0) scale(.7);
+    opacity: .22;
+  }
 }
 </style>