#
Junjie
17 小时以前 b37e141c00a123cf362fae00c1e63175d41c4bbe
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": "正在加载页面...",
    "common.loadingTab": "正在加载 “{0}” ...",
    "common.refreshingTab": "正在刷新 “{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": "菜单加载失败,请检查接口状态"
  };
  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: "正在加载页面...",
        loadingText: window.WCS_I18N ? window.WCS_I18N.tl("正在加载页面...") : "正在加载页面...",
        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 = "正在加载 “" + 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 = "正在加载 “" + 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 = "正在加载 “" + 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 = "正在加载 “控制中心” ...";
        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 = "正在刷新 “" + 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("菜单加载失败,请检查接口状态");
            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)
          });
        };