From b37e141c00a123cf362fae00c1e63175d41c4bbe Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期二, 10 三月 2026 17:01:06 +0800
Subject: [PATCH] #

---
 src/main/webapp/views/index.html |  724 +++++++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 596 insertions(+), 128 deletions(-)

diff --git a/src/main/webapp/views/index.html b/src/main/webapp/views/index.html
index b9bf30f..e6fd9eb 100644
--- a/src/main/webapp/views/index.html
+++ b/src/main/webapp/views/index.html
@@ -37,12 +37,23 @@
     }
 
     .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;
+    }
+
+    .layout-aside.is-animating {
+      pointer-events: none;
     }
 
     .layout-panel {
@@ -55,6 +66,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 +79,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 +89,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 {
@@ -88,6 +105,17 @@
     .aside-search {
       padding: 12px 12px 8px;
       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 {
@@ -109,6 +137,7 @@
     .aside-scroll {
       flex: 1;
       min-height: 0;
+      transform: translateZ(0);
     }
 
     .aside-scroll .el-scrollbar__wrap {
@@ -118,6 +147,7 @@
     .side-menu {
       border-right: none;
       background: transparent;
+      transform: translateZ(0);
     }
 
     .side-menu .el-submenu__title,
@@ -127,6 +157,7 @@
       margin: 0 10px 6px;
       border-radius: 10px;
       box-sizing: border-box;
+      backface-visibility: hidden;
     }
 
     .side-menu .el-submenu__title:hover,
@@ -228,6 +259,44 @@
       color: rgba(255, 255, 255, 0.58);
     }
 
+    .aside-loading {
+      padding: 4px 12px 12px;
+      box-sizing: border-box;
+    }
+
+    .aside-skeleton-item {
+      position: relative;
+      height: 46px;
+      margin: 0 10px 6px;
+      overflow: hidden;
+      border-radius: 10px;
+      background: rgba(255, 255, 255, 0.08);
+    }
+
+    .aside-skeleton-item::after {
+      content: "";
+      position: absolute;
+      inset: 0;
+      transform: translateX(-100%);
+      background: linear-gradient(90deg,
+      rgba(255, 255, 255, 0) 0%,
+      rgba(255, 255, 255, 0.16) 48%,
+      rgba(255, 255, 255, 0) 100%);
+      animation: asideSkeletonShimmer 1.25s ease-in-out infinite;
+    }
+
+    .aside-skeleton-item:nth-child(3n) {
+      width: calc(100% - 28px);
+    }
+
+    .aside-skeleton-item:nth-child(4n + 2) {
+      width: calc(100% - 44px);
+    }
+
+    .aside-skeleton-item:nth-child(5n) {
+      width: calc(100% - 36px);
+    }
+
     .aside-footer {
       padding: 14px 16px 16px;
       border-top: 1px solid rgba(255, 255, 255, 0.08);
@@ -300,6 +369,10 @@
       justify-content: flex-end;
       gap: 8px;
       flex-shrink: 0;
+    }
+
+    .lang-select {
+      width: 160px;
     }
 
     .header-right .el-tag {
@@ -450,6 +523,12 @@
       }
     }
 
+    @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;
@@ -485,72 +564,89 @@
 <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="鎼滅储鑿滃崟">
-        </el-input>
-      </div>
-
-      <el-scrollbar class="aside-scroll" v-loading="menuLoading">
-        <el-menu
-            ref="sideMenu"
-            class="side-menu"
-            :default-active="activeMenuKey"
-            :collapse="isCollapse"
-            :collapse-transition="false"
-            :default-openeds="defaultOpeneds"
-            unique-opened
-            background-color="transparent"
-            text-color="#c6d1df"
-            active-text-color="#ffffff">
-          <el-submenu
-              v-for="group in filteredMenus"
-              :key="'group-' + group.menuId"
-              :index="'group-' + group.menuId">
-            <template slot="title">
-              <i :class="resolveMenuIcon(group.menuCode)"></i>
-              <span>{{ group.menu }}</span>
-            </template>
-            <el-menu-item
-                v-for="item in group.subMenu"
-                :key="item.tabKey"
-                :index="item.tabKey"
-                @click="handleMenuSelect(group, item)">
-              {{ item.name }}
-            </el-menu-item>
-          </el-submenu>
-        </el-menu>
-
-        <div class="aside-empty" v-if="!menuLoading && filteredMenus.length === 0">
-          <el-empty
-              :image-size="80"
-              :description="menuKeyword ? '娌℃湁鍖归厤鑿滃崟' : '褰撳墠璐﹀彿娌℃湁鍙敤鑿滃崟'">
-          </el-empty>
+      <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">
+          <div
+              v-for="n in 8"
+              :key="'menu-skeleton-' + n"
+              class="aside-skeleton-item">
+          </div>
+        </div>
+
+        <template v-else>
+          <el-menu
+              ref="sideMenu"
+              class="side-menu"
+              :default-active="activeMenuKey"
+              :collapse="menuCollapse"
+              :collapse-transition="false"
+              :default-openeds="defaultOpeneds"
+              unique-opened
+              background-color="transparent"
+              text-color="#c6d1df"
+              active-text-color="#ffffff">
+            <el-submenu
+                v-for="group in filteredMenus"
+                :key="'group-' + group.menuId"
+                :index="'group-' + group.menuId">
+              <template slot="title">
+                <i :class="resolveMenuIcon(group.menuCode)"></i>
+                <span>{{ group.menu }}</span>
+              </template>
+              <el-menu-item
+                  v-for="item in group.subMenu"
+                  :key="item.tabKey"
+                  :index="item.tabKey"
+                  @click="handleMenuSelect(group, item)">
+                {{ item.name }}
+              </el-menu-item>
+            </el-submenu>
+          </el-menu>
+
+          <div class="aside-empty" v-if="filteredMenus.length === 0">
+            <el-empty
+                :image-size="80"
+                :description="menuKeyword ? t('index.noMatchedMenu') : t('index.noAvailableMenu')">
+            </el-empty>
+          </div>
+        </template>
       </el-scrollbar>
 
-      <div class="aside-footer" v-show="!isCollapse">
-        <div class="aside-footer-copy">漏 2026 娴欐睙涓壃绔嬪簱鎶�鏈湁闄愬叕鍙�</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">
@@ -573,7 +669,7 @@
               size="mini"
               :type="licenseTagType"
               effect="dark">
-            涓存椂璁稿彲璇佹湁鏁堟湡锛歿{ licenseDays }}澶�
+            {{ t('index.licenseDays', [licenseDays]) }}
           </el-tag>
           <el-tag
               v-if="fakeVisible"
@@ -582,8 +678,20 @@
               :type="fakeRunning ? 'danger' : 'info'"
               effect="dark"
               @click.native="toggleFakeSystem">
-            {{ fakeRunning ? '浠跨湡杩愯涓�' : '浠跨湡鏈繍琛�' }}
+            {{ fakeRunning ? t('index.fakeRunning') : t('index.fakeStopped') }}
           </el-tag>
+          <el-select
+              v-model="currentLocale"
+              size="mini"
+              class="lang-select"
+              @change="handleLocaleChange">
+            <el-option
+                v-for="item in localeOptions"
+                :key="item.tag"
+                :label="item.label"
+                :value="item.tag">
+            </el-option>
+          </el-select>
           <el-button circle size="mini" icon="el-icon-refresh" @click="refreshActiveTab"></el-button>
           <el-button circle size="mini" icon="el-icon-full-screen" @click="toggleFullScreen"></el-button>
 
@@ -594,8 +702,8 @@
               <i class="el-icon-arrow-down"></i>
             </span>
             <el-dropdown-menu slot="dropdown">
-              <el-dropdown-item command="profile">鍩烘湰璧勬枡</el-dropdown-item>
-              <el-dropdown-item command="logout" divided>閫�鍑虹櫥褰�</el-dropdown-item>
+              <el-dropdown-item command="profile">{{ t('common.profile') }}</el-dropdown-item>
+              <el-dropdown-item command="logout" divided>{{ t('common.logout') }}</el-dropdown-item>
             </el-dropdown-menu>
           </el-dropdown>
         </div>
@@ -606,7 +714,6 @@
             class="page-tabs"
             v-model="activeTab"
             type="card"
-            @tab-click="handleTabClick"
             @tab-remove="removeTab">
           <el-tab-pane
               v-for="tab in tabs"
@@ -618,10 +725,10 @@
         </el-tabs>
 
         <div class="tabs-tools">
-          <el-tooltip content="鍏抽棴鍏朵粬椤电" placement="top">
+          <el-tooltip :content="t('common.closeOtherTabs')" placement="top">
             <el-button size="mini" icon="el-icon-close" @click="closeOtherTabs"></el-button>
           </el-tooltip>
-          <el-tooltip content="杩斿洖鎺у埗涓績" placement="top">
+          <el-tooltip :content="t('common.backHome')" placement="top">
             <el-button size="mini" type="primary" icon="el-icon-house" @click="openHomeTab"></el-button>
           </el-tooltip>
         </div>
@@ -638,6 +745,7 @@
               v-for="tab in tabs"
               :key="'frame-' + tab.name"
               class="page-frame"
+              :data-tab-name="tab.name"
               v-show="activeTab === tab.name"
               :src="tab.currentSrc"
               @load="handleFrameLoad(tab.name)">
@@ -648,13 +756,13 @@
   </el-container>
 
   <el-dialog
-      title="璁稿彲璇佸嵆灏嗚繃鏈�"
+      :title="t('index.licenseExpiring')"
       :visible.sync="licenseDialogVisible"
       width="420px"
       :close-on-click-modal="false">
     <div class="license-dialog-text">{{ licenseDialogText }}</div>
     <span slot="footer">
-      <el-button type="primary" @click="licenseDialogVisible = false">鐭ラ亾浜�</el-button>
+      <el-button type="primary" @click="licenseDialogVisible = false">{{ t('common.ok') }}</el-button>
     </span>
   </el-dialog>
 
@@ -669,7 +777,7 @@
 
 <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"></script>
+<script type="text/javascript" src="../static/js/common.js?v=20260309_i18n_fix1"></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>
@@ -687,17 +795,59 @@
     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 = {
+    "app.title": "娴欐睙涓壃 - 鑷姩鍖栫珛浣撲粨搴� - WCS",
+    "app.company": "娴欐睙涓壃绔嬪簱鎶�鏈湁闄愬叕鍙�",
+    "common.loadingPage": "姝e湪鍔犺浇椤甸潰...",
+    "common.loadingTab": "姝e湪鍔犺浇 鈥渰0}鈥� ...",
+    "common.refreshingTab": "姝e湪鍒锋柊 鈥渰0}鈥� ...",
+    "common.ok": "鐭ラ亾浜�",
+    "common.prompt": "鎻愮ず",
+    "common.profile": "鍩烘湰璧勬枡",
+    "common.logout": "閫�鍑虹櫥褰�",
+    "common.closeOtherTabs": "鍏抽棴鍏朵粬椤电",
+    "common.backHome": "杩斿洖鎺у埗涓績",
+    "common.aiAssistant": "AI鍔╂墜",
+    "common.workPage": "宸ヤ綔椤甸潰",
+    "common.businessPage": "涓氬姟椤甸潰",
+    "index.searchMenu": "鎼滅储鑿滃崟",
+    "index.noMatchedMenu": "娌℃湁鍖归厤鑿滃崟",
+    "index.noAvailableMenu": "褰撳墠璐﹀彿娌℃湁鍙敤鑿滃崟",
+    "index.licenseDays": "涓存椂璁稿彲璇佹湁鏁堟湡锛歿0}澶�",
+    "index.fakeRunning": "浠跨湡杩愯涓�",
+    "index.fakeStopped": "浠跨湡鏈繍琛�",
+    "index.licenseExpiring": "璁稿彲璇佸嵆灏嗚繃鏈�",
+    "index.homeTab": "鎺у埗涓績",
+    "index.homeGroup": "瀹炴椂鐩戞帶",
+    "index.profileGroup": "璐︽埛涓績",
+    "index.versionLoading": "Version loading...",
+    "index.licenseExpireAt": "璁稿彲璇佸皢浜� {0} 杩囨湡锛屽墿浣欐湁鏁堟湡锛歿1} 澶┿��",
+    "index.confirmStopFake": "纭畾瑕佸仠姝豢鐪熸ā鎷熷悧锛�",
+    "index.confirmStartFake": "纭畾瑕佸惎鍔ㄤ豢鐪熸ā鎷熷悧锛�",
+    "index.fakeStoppedSuccess": "浠跨湡妯℃嫙宸插仠姝�",
+    "index.fakeStartedSuccess": "浠跨湡妯℃嫙宸插惎鍔�",
+    "index.operationFailed": "鎿嶄綔澶辫触",
+    "index.menuLoadFailed": "鑿滃崟鍔犺浇澶辫触",
+    "index.menuLoadFailedDetail": "鑿滃崟鍔犺浇澶辫触锛岃妫�鏌ユ帴鍙g姸鎬�"
+  };
 
   new Vue({
     el: "#app",
     data: function () {
       return {
         isCollapse: false,
-        menuLoading: false,
+        asideCompact: false,
+        menuCollapse: false,
+        showAsideExtras: true,
+        collapseAnimating: false,
+        collapseTimer: null,
+        menuLoading: true,
         pageLoading: true,
-        loadingText: "姝e湪鍔犺浇椤甸潰...",
+        loadingText: window.WCS_I18N ? window.WCS_I18N.tl("姝e湪鍔犺浇椤甸潰...") : "姝e湪鍔犺浇椤甸潰...",
         menuKeyword: "",
         menus: [],
         defaultOpeneds: [],
@@ -709,17 +859,21 @@
         licenseDays: null,
         licenseDialogVisible: false,
         licenseDialogText: "",
+        currentLocale: window.WCS_I18N ? window.WCS_I18N.getLocale() : "zh-CN",
+        localeOptions: [],
         fakeVisible: false,
         fakeRunning: false,
         fakeStatusInterval: null,
-        userName: localStorage.getItem(USER_STORAGE_KEY) || "绠$悊鍛�",
+        menuSyncVersion: 0,
+        menuSyncTimer: null,
+        userName: localStorage.getItem(USER_STORAGE_KEY) || (window.WCS_I18N ? window.WCS_I18N.tl("绠$悊鍛�") : "绠$悊鍛�"),
         aiLayerIndex: null,
         aiTipIndex: null
       };
     },
     computed: {
       asideWidth: function () {
-        return this.isCollapse ? "72px" : "248px";
+        return this.asideCompact ? "72px" : "248px";
       },
       filteredMenus: function () {
         var keyword = (this.menuKeyword || "").toLowerCase();
@@ -764,7 +918,7 @@
         return result;
       },
       activeTabMeta: function () {
-        return this.getTabByName(this.activeTab) || this.createTab(HOME_TAB_CONFIG);
+        return this.getTabByName(this.activeTab) || this.createTab(this.resolveHomeConfig());
       },
       activeTabTitle: function () {
         return this.activeTabMeta.title;
@@ -774,7 +928,7 @@
       },
       versionText: function () {
         if (!this.version) {
-          return "Version loading...";
+          return this.t("index.versionLoading");
         }
         return "Version " + this.version;
       },
@@ -806,18 +960,18 @@
         return "info";
       },
       userShortName: function () {
-        return (this.userName || "绠$悊鍛�").substring(0, 1);
+        return (this.userName || this.tl("绠$悊鍛�")).substring(0, 1);
       }
     },
     watch: {
       activeTab: function () {
         var tab = this.getTabByName(this.activeTab);
-        this.activeMenuKey = tab ? (tab.menuKey || this.findMenuKeyByUrl(tab.url)) : "";
+        this.syncMenuStateByUrl(tab ? tab.url : this.resolveHomeConfig().url);
         this.pageLoading = !!(tab && !tab.loaded);
         if (this.pageLoading) {
-          this.loadingText = "姝e湪鍔犺浇 鈥�" + tab.title + "鈥� ...";
+          this.loadingText = this.t("common.loadingTab", [tab.title]);
         }
-        document.title = (tab ? tab.title : HOME_TAB_CONFIG.title) + " - 娴欐睙涓壃 - 鑷姩鍖栫珛浣撲粨搴� - WCS";
+        this.updateDocumentTitle(tab ? tab.title : this.resolveHomeConfig().title);
         this.persistTabs();
       }
     },
@@ -828,6 +982,7 @@
       }
 
       this.restoreTabs();
+      this.bindI18n();
       this.installCompatBridge();
       this.loadSystemVersion();
       this.loadMenu();
@@ -837,7 +992,7 @@
     },
     mounted: function () {
       $("#ai-assistant-btn").html(getAiIconHtml(60, 60));
-      document.title = this.activeTabTitle + " - 娴欐睙涓壃 - 鑷姩鍖栫珛浣撲粨搴� - WCS";
+      this.updateDocumentTitle(this.activeTabTitle);
     },
     beforeDestroy: function () {
       if (this.fakeStatusInterval) {
@@ -848,12 +1003,112 @@
         clearInterval(this.userSyncTimer);
         this.userSyncTimer = null;
       }
+      if (this.menuSyncTimer) {
+        clearTimeout(this.menuSyncTimer);
+        this.menuSyncTimer = null;
+      }
+      if (this.collapseTimer) {
+        clearTimeout(this.collapseTimer);
+        this.collapseTimer = null;
+      }
       if (this.aiTipIndex) {
         layer.close(this.aiTipIndex);
         this.aiTipIndex = null;
       }
     },
     methods: {
+      t: function (key, params) {
+        var value = window.WCS_I18N ? window.WCS_I18N.t(key, params) : key;
+        if (value !== key) {
+          return value;
+        }
+        value = INDEX_I18N_FALLBACKS[key] || key;
+        if (!params) {
+          return value;
+        }
+        if (Object.prototype.toString.call(params) === "[object Array]") {
+          for (var i = 0; i < params.length; i++) {
+            value = value.replace(new RegExp("\\{" + i + "\\}", "g"), params[i]);
+          }
+          return value;
+        }
+        return value;
+      },
+      tl: function (text) {
+        return window.WCS_I18N ? window.WCS_I18N.tl(text) : text;
+      },
+      bindI18n: function () {
+        var that = this;
+        if (!window.WCS_I18N) {
+          return;
+        }
+        window.WCS_I18N.onReady(function (i18n) {
+          that.currentLocale = i18n.getLocale();
+          that.localeOptions = i18n.getLocaleOptions();
+          that.refreshI18nState();
+        });
+      },
+      refreshI18nState: function () {
+        var that = this;
+        var homeConfig = this.resolveHomeConfig();
+        var profileConfig = this.resolveProfileConfig();
+        var i;
+        HOME_TAB_CONFIG.title = homeConfig.title;
+        HOME_TAB_CONFIG.group = homeConfig.group;
+        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.updateDocumentTitle(this.activeTabTitle);
+        this.persistTabs();
+        this.updateLicenseDialogText();
+        this.$nextTick(function () {
+          if (window.WCS_I18N) {
+            window.WCS_I18N.apply(document.body);
+          }
+          that.syncAllFramesI18n();
+        });
+      },
+      handleLocaleChange: function (locale) {
+        if (window.WCS_I18N) {
+          window.WCS_I18N.setLocale(locale);
+        }
+      },
+      resolveHomeConfig: function () {
+        return {
+          title: this.t("index.homeTab"),
+          url: HOME_TAB_CONFIG.url,
+          home: true,
+          group: this.t("index.homeGroup"),
+          menuKey: HOME_TAB_CONFIG.menuKey || ""
+        };
+      },
+      resolveProfileConfig: function () {
+        return {
+          title: this.t("common.profile"),
+          url: PROFILE_TAB_CONFIG.url,
+          home: false,
+          group: this.t("index.profileGroup"),
+          menuKey: PROFILE_TAB_CONFIG.menuKey || ""
+        };
+      },
+      translateTabTitle: function (title) {
+        return this.tl(title);
+      },
+      updateDocumentTitle: function (title) {
+        document.title = title + " - " + this.t("app.title");
+      },
       resolveMenuIcon: function (code) {
         var iconMap = {
           index: "el-icon-s-home",
@@ -876,7 +1131,7 @@
           title: config.title,
           name: config.url,
           url: config.url,
-          currentSrc: config.url,
+          currentSrc: this.addNonce(config.url),
           home: !!config.home,
           group: config.group || "",
           menuKey: config.menuKey || "",
@@ -885,17 +1140,17 @@
       },
       normalizeStoredTab: function (tab) {
         var created = this.createTab({
-          title: tab.title,
+          title: this.translateTabTitle(tab.title),
           url: this.resolveViewSrc(tab.url),
           home: !!tab.home,
-          group: tab.group || "",
+          group: this.tl(tab.group || ""),
           menuKey: tab.menuKey || ""
         });
         created.loaded = false;
         return created;
       },
       restoreTabs: function () {
-        var homeTab = this.createTab(HOME_TAB_CONFIG);
+        var homeTab = this.createTab(this.resolveHomeConfig());
         var raw = localStorage.getItem(TAB_STORAGE_KEY);
         var parsed;
         var tabs = [];
@@ -937,9 +1192,9 @@
 
         this.tabs = tabs;
         this.activeTab = this.hasTab(active) ? active : homeTab.name;
-        this.loadingText = "姝e湪鍔犺浇 鈥�" + this.activeTabTitle + "鈥� ...";
+        this.loadingText = this.t("common.loadingTab", [this.activeTabTitle]);
         this.pageLoading = true;
-        document.title = this.activeTabTitle + " - 娴欐睙涓壃 - 鑷姩鍖栫珛浣撲粨搴� - WCS";
+        this.updateDocumentTitle(this.activeTabTitle);
       },
       persistTabs: function () {
         var tabs = [];
@@ -960,6 +1215,10 @@
       },
       hasTab: function (name) {
         return !!this.getTabByName(name);
+      },
+      isHomeTabUrl: function (url) {
+        var homeUrl = this.resolveHomeConfig().url;
+        return this.resolveViewSrc(url || homeUrl) === this.resolveViewSrc(homeUrl);
       },
       getTabByName: function (name) {
         var i;
@@ -992,28 +1251,29 @@
           }
         }
 
-        this.loadingText = "姝e湪鍔犺浇 鈥�" + tab.title + "鈥� ...";
+        this.loadingText = this.t("common.loadingTab", [tab.title]);
         this.pageLoading = !tab.loaded;
         this.activeTab = tab.name;
-        this.activeMenuKey = tab.menuKey || this.findMenuKeyByUrl(tab.url);
+        this.syncMenuStateByUrl(tab.url);
       },
       openHomeTab: function () {
-        this.addOrActivateTab(HOME_TAB_CONFIG);
+        this.addOrActivateTab(this.resolveHomeConfig());
       },
       openProfileTab: function () {
-        this.addOrActivateTab(PROFILE_TAB_CONFIG);
+        this.addOrActivateTab(this.resolveProfileConfig());
       },
       closeAllTabs: function () {
-        this.tabs = [this.createTab(HOME_TAB_CONFIG)];
-        this.activeTab = HOME_TAB_CONFIG.url;
-        this.activeMenuKey = "";
+        var homeConfig = this.resolveHomeConfig();
+        this.tabs = [this.createTab(homeConfig)];
+        this.activeTab = homeConfig.url;
+        this.syncMenuStateByUrl(homeConfig.url);
         this.pageLoading = true;
-        this.loadingText = "姝e湪鍔犺浇 鈥滄帶鍒朵腑蹇冣�� ...";
+        this.loadingText = this.t("common.loadingTab", [homeConfig.title]);
         this.persistTabs();
       },
       closeOtherTabs: function () {
         var active = this.getTabByName(this.activeTab);
-        var homeTab = this.createTab(HOME_TAB_CONFIG);
+        var homeTab = this.createTab(this.resolveHomeConfig());
         var result = [homeTab];
 
         if (active && active.name !== homeTab.name) {
@@ -1025,7 +1285,7 @@
       },
       removeTab: function (name) {
         var i;
-        var nextTabName = HOME_TAB_CONFIG.url;
+        var nextTabName = this.resolveHomeConfig().url;
 
         for (i = 0; i < this.tabs.length; i++) {
           if (this.tabs[i].name === name) {
@@ -1042,21 +1302,19 @@
         }
 
         if (this.tabs.length === 0) {
-          this.tabs.push(this.createTab(HOME_TAB_CONFIG));
-          nextTabName = HOME_TAB_CONFIG.url;
+          this.tabs.push(this.createTab(this.resolveHomeConfig()));
+          nextTabName = this.resolveHomeConfig().url;
         }
 
         this.activeTab = nextTabName;
         this.persistTabs();
-      },
-      handleTabClick: function () {
-        this.activeMenuKey = this.findMenuKeyByUrl(this.activeTabUrl);
       },
       handleFrameLoad: function (name) {
         var tab = this.getTabByName(name);
         if (tab) {
           tab.loaded = true;
         }
+        this.syncFrameI18n(name);
         if (this.activeTab === name) {
           this.pageLoading = false;
         }
@@ -1068,14 +1326,144 @@
         }
         tab.loaded = false;
         tab.currentSrc = this.addNonce(tab.url);
-        this.loadingText = "姝e湪鍒锋柊 鈥�" + tab.title + "鈥� ...";
+        this.loadingText = this.t("common.refreshingTab", [tab.title]);
         this.pageLoading = true;
       },
       addNonce: function (url) {
         return url + (url.indexOf("?") === -1 ? "?" : "&") + "_t=" + new Date().getTime();
       },
+      findFrameElement: function (name) {
+        var frames;
+        var i;
+        if (!this.$el) {
+          return null;
+        }
+        frames = this.$el.querySelectorAll("iframe[data-tab-name]");
+        for (i = 0; i < frames.length; i++) {
+          if (frames[i].getAttribute("data-tab-name") === name) {
+            return frames[i];
+          }
+        }
+        return null;
+      },
+      syncFrameI18n: function (name) {
+        var that = this;
+        var frame = this.findFrameElement(name);
+        var frameWindow;
+        var frameDocument;
+        var script;
+
+        function applyFrameI18n() {
+          try {
+            if (!frameWindow.WCS_I18N || typeof frameWindow.WCS_I18N.onReady !== "function") {
+              return;
+            }
+            frameWindow.WCS_I18N.setLocale(that.currentLocale || "zh-CN", false);
+            frameWindow.WCS_I18N.onReady(function (i18n) {
+              i18n.apply(frameDocument.body || frameDocument.documentElement);
+              if (frameDocument.title) {
+                frameDocument.title = i18n.tl(frameDocument.title);
+              }
+            });
+          } catch (e) {
+          }
+        }
+
+        if (!frame) {
+          return;
+        }
+        try {
+          frameWindow = frame.contentWindow;
+          frameDocument = frameWindow.document;
+        } catch (e) {
+          return;
+        }
+        try {
+          frameWindow.localStorage.setItem("wcs_lang", this.currentLocale || "zh-CN");
+        } catch (e) {
+        }
+        if (frameDocument && frameDocument.documentElement) {
+          frameDocument.documentElement.lang = this.currentLocale || "zh-CN";
+        }
+        if (frameWindow.WCS_I18N && typeof frameWindow.WCS_I18N.onReady === "function") {
+          applyFrameI18n();
+          return;
+        }
+        if (!frameDocument || !frameDocument.head || frameDocument.getElementById("wcs-i18n-bridge-script")) {
+          return;
+        }
+        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.onload = applyFrameI18n;
+        frameDocument.head.appendChild(script);
+      },
+      syncAllFramesI18n: function () {
+        var i;
+        for (i = 0; i < this.tabs.length; i++) {
+          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({
@@ -1104,6 +1492,24 @@
         }
         return "";
       },
+      findMenuGroupIndexByUrl: function (url) {
+        var normalized = this.resolveViewSrc(url);
+        var i;
+        var j;
+        var group;
+        var item;
+
+        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 (item.url === normalized) {
+              return "group-" + group.menuId;
+            }
+          }
+        }
+        return "";
+      },
       normalizeMenuData: function (data) {
         var result = [];
         var i;
@@ -1120,7 +1526,7 @@
             item = group.subMenu[j];
             subMenu.push({
               id: item.id,
-              name: item.name || item.code || "鏈懡鍚嶉〉闈�",
+              name: item.name || item.code || this.tl("鏈懡鍚嶉〉闈�"),
               code: item.code || "",
               url: this.buildMenuSrc(item.code, item.id),
               tabKey: this.buildMenuSrc(item.code, item.id)
@@ -1129,7 +1535,7 @@
 
           result.push({
             menuId: group.menuId,
-            menu: group.menu || "鏈懡鍚嶅垎缁�",
+            menu: group.menu || this.tl("鏈懡鍚嶅垎缁�"),
             menuCode: group.menuCode || "",
             subMenu: subMenu
           });
@@ -1156,7 +1562,7 @@
       },
       resolveViewSrc: function (path) {
         if (!path) {
-          return HOME_TAB_CONFIG.url;
+          return this.resolveHomeConfig().url;
         }
         if (/^https?:\/\//.test(path) || path.indexOf(baseUrl) === 0) {
           return path;
@@ -1188,6 +1594,65 @@
           menu.close(openedMenus[i]);
         }
       },
+      syncMenuStateByUrl: function (url) {
+        var targetUrl = this.resolveViewSrc(url || this.resolveHomeConfig().url);
+        var activeMenuKey = "";
+        var groupIndex = "";
+        var that = this;
+        var syncVersion;
+        var applyMenuState;
+
+        if (!this.isHomeTabUrl(targetUrl)) {
+          activeMenuKey = this.findMenuKeyByUrl(targetUrl);
+          if (activeMenuKey) {
+            groupIndex = this.findMenuGroupIndexByUrl(targetUrl);
+          }
+        }
+
+        this.activeMenuKey = activeMenuKey;
+        this.defaultOpeneds = groupIndex ? [groupIndex] : [];
+        this.menuSyncVersion += 1;
+        syncVersion = this.menuSyncVersion;
+        applyMenuState = function () {
+          var menu = that.$refs.sideMenu;
+          var openedMenus;
+          if (syncVersion !== that.menuSyncVersion) {
+            return;
+          }
+          if (!menu) {
+            return;
+          }
+          if (that.menuCollapse) {
+            return;
+          }
+          openedMenus = menu.openedMenus ? menu.openedMenus.slice() : [];
+          if (!groupIndex) {
+            if (openedMenus.length) {
+              that.collapseAllMenus();
+            }
+            return;
+          }
+          if (openedMenus.indexOf(groupIndex) > -1) {
+            return;
+          }
+          if (openedMenus.length) {
+            that.collapseAllMenus();
+          }
+          menu.open(groupIndex);
+        };
+
+        this.$nextTick(function () {
+          applyMenuState();
+          if (that.menuSyncTimer) {
+            clearTimeout(that.menuSyncTimer);
+            that.menuSyncTimer = null;
+          }
+          that.menuSyncTimer = setTimeout(function () {
+            applyMenuState();
+            that.menuSyncTimer = null;
+          }, 160);
+        });
+      },
       loadMenu: function () {
         var that = this;
         this.menuLoading = true;
@@ -1199,20 +1664,16 @@
             that.menuLoading = false;
             if (res.code === 200) {
               that.menus = that.normalizeMenuData(res.data || []);
-              that.defaultOpeneds = [];
-              that.activeMenuKey = that.findMenuKeyByUrl(that.activeTabUrl);
-              that.$nextTick(function () {
-                that.collapseAllMenus();
-              });
+              that.syncMenuStateByUrl(that.activeTabUrl);
             } else if (res.code === 403) {
               top.location.href = baseUrl + "/login";
             } else {
-              that.$message.error(res.msg || "鑿滃崟鍔犺浇澶辫触");
+              that.$message.error(res.msg || that.t("index.menuLoadFailed"));
             }
           },
           error: function () {
             that.menuLoading = false;
-            that.$message.error("鑿滃崟鍔犺浇澶辫触锛岃妫�鏌ユ帴鍙g姸鎬�");
+            that.$message.error(that.t("index.menuLoadFailedDetail"));
           }
         });
       },
@@ -1259,6 +1720,7 @@
       showPopup: function (days) {
         var currentDate;
         var expiryDate;
+        var formattedDate;
 
         if (days === "" || days === null || typeof days === "undefined") {
           this.hidePopup();
@@ -1268,9 +1730,15 @@
         currentDate = new Date();
         expiryDate = new Date();
         expiryDate.setDate(currentDate.getDate() + Number(days) + 1);
-        this.licenseDialogText = "璁稿彲璇佸皢浜� " + new Intl.DateTimeFormat("zh-CN").format(expiryDate) +
-          " 杩囨湡锛屽墿浣欐湁鏁堟湡锛�" + days + " 澶┿��";
+        formattedDate = new Intl.DateTimeFormat(this.currentLocale || "zh-CN").format(expiryDate);
+        this.licenseDialogText = this.t("index.licenseExpireAt", [formattedDate, days]);
         this.licenseDialogVisible = true;
+      },
+      updateLicenseDialogText: function () {
+        if (this.licenseDays === "" || this.licenseDays === null || typeof this.licenseDays === "undefined" || this.licenseDays > 15) {
+          return;
+        }
+        this.showPopup(this.licenseDays);
       },
       hidePopup: function () {
         this.licenseDialogVisible = false;
@@ -1318,17 +1786,17 @@
 
         if (this.fakeRunning) {
           url = baseUrl + "/openapi/stopFakeSystem";
-          text = "纭畾瑕佸仠姝豢鐪熸ā鎷熷悧锛�";
-          successMsg = "浠跨湡妯℃嫙宸插仠姝�";
+          text = this.t("index.confirmStopFake");
+          successMsg = this.t("index.fakeStoppedSuccess");
           running = false;
         } else {
           url = baseUrl + "/openapi/startFakeSystem";
-          text = "纭畾瑕佸惎鍔ㄤ豢鐪熸ā鎷熷悧锛�";
-          successMsg = "浠跨湡妯℃嫙宸插惎鍔�";
+          text = this.t("index.confirmStartFake");
+          successMsg = this.t("index.fakeStartedSuccess");
           running = true;
         }
 
-        this.$confirm(text, "鎻愮ず", {
+        this.$confirm(text, this.t("common.prompt"), {
           type: "warning"
         }).then(function () {
           $.ajax({
@@ -1340,7 +1808,7 @@
                 that.fakeRunning = running;
                 that.$message.success(successMsg);
               } else {
-                that.$message.error(res.msg || "鎿嶄綔澶辫触");
+                that.$message.error(res.msg || that.t("index.operationFailed"));
               }
             }
           });
@@ -1355,7 +1823,7 @@
       },
       showAiTip: function () {
         this.hideAiTip();
-        this.aiTipIndex = layer.tips("AI鍔╂墜", "#ai-assistant-btn", {
+        this.aiTipIndex = layer.tips(this.t("common.aiAssistant"), "#ai-assistant-btn", {
           tips: [1, "#333"],
           time: -1
         });
@@ -1436,7 +1904,7 @@
       startUserSync: function () {
         var that = this;
         this.userSyncTimer = setInterval(function () {
-          that.userName = localStorage.getItem(USER_STORAGE_KEY) || "绠$悊鍛�";
+          that.userName = localStorage.getItem(USER_STORAGE_KEY) || that.tl("绠$悊鍛�");
         }, 1000);
       },
       installCompatBridge: function () {
@@ -1448,7 +1916,7 @@
         };
         window.admin.changeTheme = window.admin.changeTheme || function () {};
         window.admin.activeNav = function (url) {
-          that.activeMenuKey = that.findMenuKeyByUrl(that.resolveViewSrc(url));
+          that.syncMenuStateByUrl(that.resolveViewSrc(url));
         };
 
         window.index = window.index || {};
@@ -1459,10 +1927,10 @@
           }
           url = that.resolveViewSrc(param.menuPath);
           that.addOrActivateTab({
-            title: that.stripTags(param.menuName) || "宸ヤ綔椤甸潰",
+            title: that.stripTags(param.menuName) || that.t("common.workPage"),
             url: url,
             home: false,
-            group: "涓氬姟椤甸潰",
+            group: that.t("common.businessPage"),
             menuKey: that.findMenuKeyByUrl(url)
           });
         };
@@ -1474,10 +1942,10 @@
           }
           url = that.resolveViewSrc(param.menuPath);
           that.addOrActivateTab({
-            title: that.stripTags(param.menuName) || HOME_TAB_CONFIG.title,
+            title: that.stripTags(param.menuName) || that.resolveHomeConfig().title,
             url: url,
             home: true,
-            group: HOME_TAB_CONFIG.group,
+            group: that.resolveHomeConfig().group,
             menuKey: that.findMenuKeyByUrl(url)
           });
         };

--
Gitblit v1.9.1