From be1cd9e5b30097ca427a9c2b7b054b28854e410a Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期三, 11 三月 2026 13:21:36 +0800
Subject: [PATCH] #

---
 src/main/webapp/views/index.html |  472 ++++++++++++++++++++++++++++++++++++++++------------------
 1 files changed, 321 insertions(+), 151 deletions(-)

diff --git a/src/main/webapp/views/index.html b/src/main/webapp/views/index.html
index 9a51064..37d8d39 100644
--- a/src/main/webapp/views/index.html
+++ b/src/main/webapp/views/index.html
@@ -37,12 +37,25 @@
     }
 
     .layout-aside {
+      position: relative;
+      flex-shrink: 0;
       display: flex;
       flex-direction: column;
       overflow: hidden;
       border-radius: 16px;
       background: linear-gradient(180deg, #16324d 0%, #0d2237 100%);
       box-shadow: 0 14px 32px rgba(22, 50, 77, 0.18);
+      transform: translateZ(0);
+      backface-visibility: hidden;
+      will-change: width;
+      transition: width 0.22s cubic-bezier(0.22, 1, 0.36, 1),
+      box-shadow 0.22s ease;
+      -webkit-user-select: none;
+      user-select: none;
+    }
+
+    .layout-aside.is-animating {
+      pointer-events: none;
     }
 
     .layout-panel {
@@ -55,6 +68,8 @@
       background: #f5f7fa;
       box-shadow: 0 14px 32px rgba(83, 104, 129, 0.12);
       border: 1px solid #e4eaf2;
+      transform: translateZ(0);
+      backface-visibility: hidden;
     }
 
     .aside-logo {
@@ -66,6 +81,8 @@
       box-sizing: border-box;
       color: #fff;
       border-bottom: 1px solid rgba(255, 255, 255, 0.08);
+      transition: min-height 0.22s cubic-bezier(0.22, 1, 0.36, 1),
+      padding 0.22s cubic-bezier(0.22, 1, 0.36, 1);
     }
 
     .aside-logo-image {
@@ -74,6 +91,8 @@
       max-width: 178px;
       height: auto;
       object-fit: contain;
+      transform: translateZ(0);
+      transition: max-width 0.22s cubic-bezier(0.22, 1, 0.36, 1);
     }
 
     .aside-logo.is-collapse {
@@ -90,12 +109,25 @@
       box-sizing: border-box;
     }
 
+    .aside-fade-enter-active,
+    .aside-fade-leave-active {
+      transition: opacity 0.14s ease, transform 0.18s ease;
+    }
+
+    .aside-fade-enter,
+    .aside-fade-leave-to {
+      opacity: 0;
+      transform: translate3d(-10px, 0, 0);
+    }
+
     .aside-search .el-input__inner {
       height: 36px;
       line-height: 36px;
       border: 1px solid rgba(255, 255, 255, 0.10);
       background: rgba(255, 255, 255, 0.08);
       color: #fff;
+      -webkit-user-select: text;
+      user-select: text;
     }
 
     .aside-search .el-input__inner::placeholder {
@@ -109,6 +141,7 @@
     .aside-scroll {
       flex: 1;
       min-height: 0;
+      transform: translateZ(0);
     }
 
     .aside-scroll .el-scrollbar__wrap {
@@ -118,6 +151,7 @@
     .side-menu {
       border-right: none;
       background: transparent;
+      transform: translateZ(0);
     }
 
     .side-menu .el-submenu__title,
@@ -127,6 +161,7 @@
       margin: 0 10px 6px;
       border-radius: 10px;
       box-sizing: border-box;
+      backface-visibility: hidden;
     }
 
     .side-menu .el-submenu__title:hover,
@@ -384,6 +419,8 @@
       background: #fff;
       border-bottom: 1px solid #e8edf5;
       box-sizing: border-box;
+      -webkit-user-select: none;
+      user-select: none;
     }
 
     .page-tabs {
@@ -402,6 +439,8 @@
     .page-tabs .el-tabs__item {
       height: 38px;
       line-height: 38px;
+      -webkit-user-select: none;
+      user-select: none;
     }
 
     .tabs-tools {
@@ -410,6 +449,8 @@
       gap: 8px;
       padding-bottom: 6px;
       flex-shrink: 0;
+      -webkit-user-select: none;
+      user-select: none;
     }
 
     .content-main {
@@ -468,45 +509,10 @@
       word-break: break-word;
     }
 
-    @keyframes slideInRight {
-      from {
-        transform: translate3d(100%, 0, 0);
-        opacity: 0;
-      }
-
-      to {
-        transform: translate3d(0, 0, 0);
-        opacity: 1;
-      }
-    }
-
-    @keyframes slideOutRight {
-      from {
-        transform: translate3d(0, 0, 0);
-        opacity: 1;
-      }
-
-      to {
-        transform: translate3d(100%, 0, 0);
-        opacity: 0;
-      }
-    }
-
     @keyframes asideSkeletonShimmer {
       100% {
         transform: translateX(100%);
       }
-    }
-
-    .ai-drawer-layer {
-      box-shadow: -8px 0 24px rgba(0, 0, 0, 0.15) !important;
-      border-radius: 8px 0 0 8px !important;
-      overflow: hidden;
-      animation: slideInRight 0.5s cubic-bezier(0.16, 1, 0.3, 1);
-    }
-
-    .ai-drawer-layer-close {
-      animation: slideOutRight 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards !important;
     }
 
     .ai-assistant-btn {
@@ -515,6 +521,87 @@
       bottom: 40px;
       z-index: 9999;
       cursor: pointer;
+    }
+
+    .ai-assistant-mask {
+      position: fixed;
+      inset: 0;
+      z-index: 9997;
+      background: rgba(15, 23, 42, 0.10);
+      backdrop-filter: blur(3px);
+    }
+
+    .ai-assistant-drawer {
+      position: fixed;
+      top: 0;
+      right: 0;
+      z-index: 9998;
+      width: min(600px, 100vw);
+      height: 100vh;
+      display: flex;
+      flex-direction: column;
+      background: #fff;
+      border-radius: 12px 0 0 12px;
+      box-shadow: -10px 0 28px rgba(15, 23, 42, 0.18);
+      overflow: hidden;
+    }
+
+    .ai-drawer-header {
+      height: 54px;
+      padding: 0 16px 0 20px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      border-bottom: 1px solid rgba(226, 232, 240, 0.92);
+      background: rgba(248, 251, 255, 0.96);
+      color: #243447;
+      font-size: 15px;
+      font-weight: 700;
+      box-sizing: border-box;
+    }
+
+    .ai-drawer-close {
+      width: 32px;
+      height: 32px;
+      border: none;
+      border-radius: 10px;
+      background: transparent;
+      color: #5f7084;
+      cursor: pointer;
+      font-size: 16px;
+    }
+
+    .ai-drawer-close:hover {
+      background: rgba(236, 243, 249, 0.92);
+      color: #24405c;
+    }
+
+    .ai-drawer-frame {
+      flex: 1;
+      width: 100%;
+      border: none;
+      background: #fff;
+    }
+
+    .ai-mask-enter-active,
+    .ai-mask-leave-active {
+      transition: opacity .22s ease;
+    }
+
+    .ai-mask-enter,
+    .ai-mask-leave-to {
+      opacity: 0;
+    }
+
+    .ai-panel-enter-active,
+    .ai-panel-leave-active {
+      transition: transform .28s cubic-bezier(0.22, 1, 0.36, 1), opacity .22s ease;
+    }
+
+    .ai-panel-enter,
+    .ai-panel-leave-to {
+      transform: translate3d(100%, 0, 0);
+      opacity: 0;
     }
 
     @media (max-width: 1440px) {
@@ -533,20 +620,25 @@
 <body>
 <div id="app" v-cloak>
   <el-container class="shell-container">
-    <el-aside class="layout-aside" :width="asideWidth">
-      <div class="aside-logo" :class="{ 'is-collapse': isCollapse }">
+    <el-aside
+        class="layout-aside"
+        :class="{ 'is-animating': collapseAnimating }"
+        :width="asideWidth">
+      <div class="aside-logo" :class="{ 'is-collapse': asideCompact }">
         <img class="aside-logo-image" src="../static/images/zy-logo.png" alt="娴欐睙涓壃">
       </div>
 
-      <div class="aside-search" v-show="!isCollapse">
-        <el-input
-            v-model.trim="menuKeyword"
-            size="small"
-            clearable
-            prefix-icon="el-icon-search"
-            :placeholder="t('index.searchMenu')">
-        </el-input>
-      </div>
+      <transition name="aside-fade">
+        <div class="aside-search" v-show="showAsideExtras">
+          <el-input
+              v-model.trim="menuKeyword"
+              size="small"
+              clearable
+              prefix-icon="el-icon-search"
+              :placeholder="t('index.searchMenu')">
+          </el-input>
+        </div>
+      </transition>
 
       <el-scrollbar class="aside-scroll">
         <div v-if="menuLoading" class="aside-loading" aria-hidden="true">
@@ -562,7 +654,7 @@
               ref="sideMenu"
               class="side-menu"
               :default-active="activeMenuKey"
-              :collapse="isCollapse"
+              :collapse="menuCollapse"
               :collapse-transition="false"
               :default-openeds="defaultOpeneds"
               unique-opened
@@ -596,19 +688,21 @@
         </template>
       </el-scrollbar>
 
-      <div class="aside-footer" v-show="!isCollapse">
-        <div class="aside-footer-copy">漏 2026 {{ t('app.company') }}</div>
-        <div class="aside-footer-version">
-          <span class="aside-footer-version-text">{{ versionText }}</span>
-          <el-tag
-              v-if="versionType"
-              size="mini"
-              :type="versionTagType"
-              effect="dark">
-            {{ versionTypeTag }}
-          </el-tag>
+      <transition name="aside-fade">
+        <div class="aside-footer" v-show="showAsideExtras">
+          <div class="aside-footer-copy">漏 2026 {{ t('app.company') }}</div>
+          <div class="aside-footer-version">
+            <span class="aside-footer-version-text">{{ versionText }}</span>
+            <el-tag
+                v-if="versionType"
+                size="mini"
+                :type="versionTagType"
+                effect="dark">
+              {{ versionTypeTag }}
+            </el-tag>
+          </div>
         </div>
-      </div>
+      </transition>
     </el-aside>
 
     <el-container class="layout-panel">
@@ -728,18 +822,43 @@
     </span>
   </el-dialog>
 
-  <div
-      id="ai-assistant-btn"
-      class="ai-assistant-btn"
-      @mouseenter="showAiTip"
-      @mouseleave="hideAiTip"
-      @click="openAiAssistant">
-  </div>
+  <el-tooltip :content="t('common.aiAssistant')" placement="left">
+    <div
+        id="ai-assistant-btn"
+        class="ai-assistant-btn"
+        @click="openAiAssistant">
+    </div>
+  </el-tooltip>
+
+  <transition name="ai-mask">
+    <div
+        v-if="aiAssistantVisible"
+        class="ai-assistant-mask"
+        @click="closeAiAssistant">
+    </div>
+  </transition>
+
+  <transition name="ai-panel">
+    <div
+        v-if="aiAssistantVisible"
+        class="ai-assistant-drawer">
+      <div class="ai-drawer-header">
+        <span>{{ t('common.aiAssistant') }}</span>
+        <button type="button" class="ai-drawer-close" @click="closeAiAssistant">
+          <i class="el-icon-close"></i>
+        </button>
+      </div>
+      <iframe
+          v-if="aiAssistantMounted"
+          class="ai-drawer-frame"
+          :src="aiAssistantSrc">
+      </iframe>
+    </div>
+  </transition>
 </div>
 
 <script type="text/javascript" src="../static/js/jquery/jquery-3.3.1.min.js"></script>
-<script type="text/javascript" src="../static/js/layer/layer.js"></script>
-<script type="text/javascript" src="../static/js/common.js?v=20260309_i18n_fix1"></script>
+<script type="text/javascript" src="../static/js/common.js"></script>
 <script type="text/javascript" src="../static/vue/js/vue.min.js"></script>
 <script type="text/javascript" src="../static/vue/element/element.js"></script>
 <script>
@@ -757,6 +876,8 @@
     group: "璐︽埛涓績",
     menuKey: ""
   };
+  var SIDEBAR_TRANSITION_MS = 220;
+  var SIDEBAR_EXPAND_CONTENT_DELAY = 180;
   var TAB_STORAGE_KEY = "wcs-element-home-tabs";
   var USER_STORAGE_KEY = "username";
   var INDEX_I18N_FALLBACKS = {
@@ -800,6 +921,11 @@
     data: function () {
       return {
         isCollapse: false,
+        asideCompact: false,
+        menuCollapse: false,
+        showAsideExtras: true,
+        collapseAnimating: false,
+        collapseTimer: null,
         menuLoading: true,
         pageLoading: true,
         loadingText: window.WCS_I18N ? window.WCS_I18N.tl("姝e湪鍔犺浇椤甸潰...") : "姝e湪鍔犺浇椤甸潰...",
@@ -822,13 +948,14 @@
         menuSyncVersion: 0,
         menuSyncTimer: null,
         userName: localStorage.getItem(USER_STORAGE_KEY) || (window.WCS_I18N ? window.WCS_I18N.tl("绠$悊鍛�") : "绠$悊鍛�"),
-        aiLayerIndex: null,
-        aiTipIndex: null
+        aiAssistantVisible: false,
+        aiAssistantMounted: false,
+        aiAssistantSrc: baseUrl + "/views/ai/diagnosis.html"
       };
     },
     computed: {
       asideWidth: function () {
-        return this.isCollapse ? "72px" : "248px";
+        return this.asideCompact ? "72px" : "248px";
       },
       filteredMenus: function () {
         var keyword = (this.menuKeyword || "").toLowerCase();
@@ -962,9 +1089,9 @@
         clearTimeout(this.menuSyncTimer);
         this.menuSyncTimer = null;
       }
-      if (this.aiTipIndex) {
-        layer.close(this.aiTipIndex);
-        this.aiTipIndex = null;
+      if (this.collapseTimer) {
+        clearTimeout(this.collapseTimer);
+        this.collapseTimer = null;
       }
     },
     methods: {
@@ -1009,17 +1136,7 @@
         PROFILE_TAB_CONFIG.title = profileConfig.title;
         PROFILE_TAB_CONFIG.group = profileConfig.group;
         for (i = 0; i < this.tabs.length; i++) {
-          if (this.isHomeTabUrl(this.tabs[i].url)) {
-            this.tabs[i].title = homeConfig.title;
-            this.tabs[i].group = homeConfig.group;
-            this.tabs[i].home = true;
-          } else if (this.resolveViewSrc(this.tabs[i].url) === this.resolveViewSrc(profileConfig.url)) {
-            this.tabs[i].title = profileConfig.title;
-            this.tabs[i].group = profileConfig.group;
-          } else {
-            this.tabs[i].title = this.translateTabTitle(this.tabs[i].title);
-            this.tabs[i].group = this.tl(this.tabs[i].group);
-          }
+          this.syncTabMeta(this.tabs[i], homeConfig, profileConfig);
         }
         this.updateDocumentTitle(this.activeTabTitle);
         this.persistTabs();
@@ -1056,6 +1173,56 @@
       },
       translateTabTitle: function (title) {
         return this.tl(title);
+      },
+      findMenuMeta: function (tab) {
+        var normalizedUrl;
+        var i;
+        var j;
+        var group;
+        var item;
+        if (!tab) {
+          return null;
+        }
+        normalizedUrl = this.resolveViewSrc(tab.url);
+        for (i = 0; i < this.menus.length; i++) {
+          group = this.menus[i];
+          for (j = 0; j < group.subMenu.length; j++) {
+            item = group.subMenu[j];
+            if ((tab.menuKey && item.tabKey === tab.menuKey) || item.url === normalizedUrl) {
+              return {
+                group: group,
+                item: item
+              };
+            }
+          }
+        }
+        return null;
+      },
+      syncTabMeta: function (tab, homeConfig, profileConfig) {
+        var menuMeta;
+        if (!tab) {
+          return;
+        }
+        if (this.isHomeTabUrl(tab.url)) {
+          tab.title = homeConfig.title;
+          tab.group = homeConfig.group;
+          tab.home = true;
+          return;
+        }
+        if (this.resolveViewSrc(tab.url) === this.resolveViewSrc(profileConfig.url)) {
+          tab.title = profileConfig.title;
+          tab.group = profileConfig.group;
+          return;
+        }
+        menuMeta = this.findMenuMeta(tab);
+        if (menuMeta) {
+          tab.title = menuMeta.item.name;
+          tab.group = menuMeta.group.menu;
+          tab.menuKey = menuMeta.item.tabKey || tab.menuKey;
+          return;
+        }
+        tab.title = this.translateTabTitle(tab.title);
+        tab.group = this.tl(tab.group);
       },
       updateDocumentTitle: function (title) {
         document.title = title + " - " + this.t("app.title");
@@ -1346,7 +1513,7 @@
         script = frameDocument.createElement("script");
         script.id = "wcs-i18n-bridge-script";
         script.type = "text/javascript";
-        script.src = baseUrl + "/static/js/common.js?v=20260309_i18n_fix1";
+        script.src = baseUrl + "/static/js/common.js";
         script.onload = applyFrameI18n;
         frameDocument.head.appendChild(script);
       },
@@ -1356,8 +1523,65 @@
           this.syncFrameI18n(this.tabs[i].name);
         }
       },
+      clearCollapseTimer: function () {
+        if (this.collapseTimer) {
+          clearTimeout(this.collapseTimer);
+          this.collapseTimer = null;
+        }
+      },
+      nextFrame: function (callback) {
+        if (window.requestAnimationFrame) {
+          window.requestAnimationFrame(function () {
+            callback();
+          });
+          return;
+        }
+        setTimeout(callback, 16);
+      },
+      finishCollapseStage: function (callback, delay) {
+        var that = this;
+        this.clearCollapseTimer();
+        this.collapseTimer = setTimeout(function () {
+          that.collapseTimer = null;
+          callback();
+        }, delay);
+      },
       toggleCollapse: function () {
-        this.isCollapse = !this.isCollapse;
+        var that = this;
+
+        if (this.collapseAnimating) {
+          return;
+        }
+
+        this.collapseAnimating = true;
+
+        if (this.isCollapse) {
+          this.isCollapse = false;
+          this.asideCompact = false;
+          this.nextFrame(function () {
+            that.finishCollapseStage(function () {
+              that.menuCollapse = false;
+              that.showAsideExtras = true;
+              that.collapseAnimating = false;
+              that.$nextTick(function () {
+                that.syncMenuStateByUrl(that.activeTabUrl);
+              });
+            }, SIDEBAR_EXPAND_CONTENT_DELAY);
+          });
+          return;
+        }
+
+        this.isCollapse = true;
+        this.showAsideExtras = false;
+        this.menuCollapse = true;
+        this.$nextTick(function () {
+          that.nextFrame(function () {
+            that.asideCompact = true;
+            that.finishCollapseStage(function () {
+              that.collapseAnimating = false;
+            }, SIDEBAR_TRANSITION_MS);
+          });
+        });
       },
       handleMenuSelect: function (group, item) {
         this.addOrActivateTab({
@@ -1516,6 +1740,9 @@
           if (!menu) {
             return;
           }
+          if (that.menuCollapse) {
+            return;
+          }
           openedMenus = menu.openedMenus ? menu.openedMenus.slice() : [];
           if (!groupIndex) {
             if (openedMenus.length) {
@@ -1555,6 +1782,7 @@
             that.menuLoading = false;
             if (res.code === 200) {
               that.menus = that.normalizeMenuData(res.data || []);
+              that.refreshI18nState();
               that.syncMenuStateByUrl(that.activeTabUrl);
             } else if (res.code === 403) {
               top.location.href = baseUrl + "/login";
@@ -1712,72 +1940,14 @@
           document.exitFullscreen();
         }
       },
-      showAiTip: function () {
-        this.hideAiTip();
-        this.aiTipIndex = layer.tips(this.t("common.aiAssistant"), "#ai-assistant-btn", {
-          tips: [1, "#333"],
-          time: -1
-        });
-      },
-      hideAiTip: function () {
-        if (this.aiTipIndex) {
-          layer.close(this.aiTipIndex);
-          this.aiTipIndex = null;
-        }
-      },
       openAiAssistant: function () {
-        var that = this;
-        var $layero;
-        var $shade;
-
-        this.hideAiTip();
-
-        if (this.aiLayerIndex !== null && $("#layui-layer" + this.aiLayerIndex).length > 0) {
-          $layero = $("#layui-layer" + this.aiLayerIndex);
-          $shade = $("#layui-layer-shade" + this.aiLayerIndex);
-
-          $shade.show().css("opacity", 0.1);
-          $layero.show();
-          $layero.removeClass("ai-drawer-layer-close");
-          $layero.removeClass("ai-drawer-layer");
-          void $layero.get(0).offsetWidth;
-          $layero.addClass("ai-drawer-layer");
-          return;
+        if (!this.aiAssistantMounted) {
+          this.aiAssistantMounted = true;
         }
-
-        layer.open({
-          type: 2,
-          title: false,
-          closeBtn: 0,
-          shadeClose: false,
-          shade: 0.1,
-          area: ["600px", "100%"],
-          offset: "r",
-          anim: -1,
-          isOutAnim: false,
-          skin: "ai-drawer-layer",
-          content: "ai/diagnosis.html",
-          success: function (layero, index) {
-            var shadeId;
-            var $shadeEl;
-
-            that.aiLayerIndex = index;
-            shadeId = layero.attr("id").replace("layui-layer", "layui-layer-shade");
-            $shadeEl = $("#" + shadeId);
-            $shadeEl.css({
-              "backdrop-filter": "blur(3px)",
-              transition: "opacity 0.8s"
-            });
-            $shadeEl.off("click.aiAssistant").on("click.aiAssistant", function () {
-              layero.addClass("ai-drawer-layer-close");
-              $shadeEl.css("opacity", 0);
-              setTimeout(function () {
-                layero.hide();
-                $shadeEl.hide();
-              }, 400);
-            });
-          }
-        });
+        this.aiAssistantVisible = true;
+      },
+      closeAiAssistant: function () {
+        this.aiAssistantVisible = false;
       },
       handleUserCommand: function (command) {
         if (command === "profile") {

--
Gitblit v1.9.1