<!--
 * @Author: your name
 * @Date: 2026-05-21 15:49:05
 * @LastEditTime: 2026-05-21 15:49:16
 * @LastEditors: milo-MacBook-Pro.local
 * @Description: In User Settings Edit
 * @FilePath: /performance-test/src/views/home/components/MenuDt.vue
-->
<template>
  <el-aside
    :width="!isCollapse ? '250px' : '100px'"
    :class="['asideBox', isTechBlueTheme ? 'asideBox--tech' : 'asideBox--legacy']"
  >
    <div class="homeBox"></div>
    <el-menu
      collapse-transition
      ref="menu"
      :class="['mt-3 el-menu-vertical-demo', menuThemeClass]"
      @open="handleOpen"
      @close="handleClose"
      :background-color="menuBackgroundColor"
      :text-color="menuTextColor"
      :active-text-color="menuActiveTextColor"
      :router="false"
      :default-active="menuDefaultActive"
      :collapse="isCollapse"
      :default-openeds="openMenus"
    >
      <template v-for="item in routerList">
        <!-- 有子菜单 -->
        <el-submenu
          v-if="
            item.children && item.children.length && item.meta?.hidden === false
          "
          :index="normalizedMenuParentIndex(item)"
          :popper-append-to-body="true"
          :popper-class="menuPopperClass"
        >
          <template slot="title">
            <i v-if="isElPrefix(item.iconName)" class="el-icon-menu"></i>
            <i v-else class="svnIcon">
              <svg-icon :icon-class="item.iconName" />
            </i>
            <span>{{ item.name }}</span>
          </template>

          <!-- 子菜单项 -->
          <el-menu-item
            v-for="child in item.children"
            v-if="child.meta?.hidden === false"
            :key="child.id"
            class="menu-dt-leaf"
            :class="{ 'dt-menu-leaf-active': menuLeafIsActive(child) }"
            :index="menuItemIndex(child)"
            @click="handleChangeMenuUrl(child)"
          >
            <i v-if="isElPrefix(child.iconName)" class="el-icon-menu"></i>
            <i v-else class="svnIcon">
              <svg-icon :icon-class="child.iconName" />
            </i>
            <span>{{ child.name }}</span>
          </el-menu-item>
        </el-submenu>

        <!-- 没有子菜单 -->
        <el-tooltip
          v-else-if="item.meta?.hidden === false"
          class="box-item"
          effect="dark"
          :content="item.name"
          placement="right-start"
        >
          <el-menu-item
            class="menu-dt-leaf"
            :class="{ 'dt-menu-leaf-active': menuLeafIsActive(item) }"
            :key="item.id"
            :index="menuItemIndex(item)"
            @click="handleChangeMenuUrl(item)"
          >
            <i v-if="isElPrefix(item.iconName)" class="el-icon-menu"></i>
            <i v-else-if="item.iconName" class="svnIcon">
              <svg-icon :icon-class="item.iconName" />
            </i>
            <i
              class="svnIcon"
              v-else-if="item.path === '/home/laserRangeFinder'"
            >
              <svg-icon
                style="width: 22px; height: 22px"
                icon-class="laserRangeFinder"
              />
            </i>
            <span>{{ item.name }}</span>
          </el-menu-item>
        </el-tooltip>
      </template>
    </el-menu>

    <!-- 展开/收起 -->
    <div class="foldBox flexCenter">
      <span
        v-show="isCollapse"
        class="el-icon-s-unfold icon"
        @click.stop="isCollapse = false"
        >展开</span
      >
      <el-switch
        v-show="!isCollapse"
        :value="isExpanded"
        @change="toggleAllMenus"
      />
      <span
        v-show="!isCollapse"
        class="el-icon-s-fold icon"
        @click.stop="isCollapse = true"
        >收起</span
      >
    </div>
  </el-aside>
</template>

<script>
import { mapActions, mapState } from "vuex";

export default {
  data() {
    return {
      isExpanded: false,
      openMenus: [],
      isCollapse: true,
      activeIndex: false,
      keyObject: {},
      activeTextColor: "#ffffff",
      /** Element UI 会先响应 default-active 变 '' 并清空内部高亮，解析失败时用最近一次有效 index 兜底，避免闪一下又消失 */
      staleMenuFallbackIndex: "",
      routerList: [
        {
          id: 1,
          path: "/home/cockpitManage",
          name: "首页",
          iconName: "gps",
          meta: { hidden: false },
        },
      ],
    };
  },
  created() {
    this.routerList = [
      ...this.routerList,
      ...this.$store.state.auth.dynamicRouter,
    ];
  },
  computed: {
    ...mapState({
      currentMenuIndex: (state) => state.breadStore?.currentUrl?.routeUrl,
      currentTheme: (state) => state.themes?.theme,
    }),
    isTechBlueTheme() {
      return this.currentTheme === "tech-blue";
    },
    menuThemeClass() {
      return this.isTechBlueTheme ? "menu-dt-dark" : "menu-dt-light";
    },
    menuBackgroundColor() {
      return this.isTechBlueTheme ? "#141414" : "#eff1f3";
    },
    menuTextColor() {
      return this.isTechBlueTheme ? "#999999" : "#606266";
    },
    menuActiveTextColor() {
      if (this.isTechBlueTheme) return "#ffffff";
      const primary = getComputedStyle(document.documentElement)
        .getPropertyValue("--primary-color")
        .trim();
      return primary || "#82780c";
    },
    menuPopperClass() {
      return this.isTechBlueTheme ? "menu-dt-dark-popup" : "";
    },
    allMenuIndexes() {
      return this.routerList.map((item) =>
        item.children && item.children.length && item.meta?.hidden === false
          ? this.normalizedMenuParentIndex(item)
          : this.menuItemIndex(item),
      );
    },
    /** 传给 el-menu 的 default-active：优先按路由解析，失败时若在「同一菜单页」则用 stale 占位，防止 Element 把 activeIndex 清空 */
    menuDefaultActive() {
      const direct = this.resolveMenuActiveFromRoute(this.$route);
      if (direct) return direct;
      if (
        this.staleMenuFallbackIndex &&
        this.routeMatchesMenuIndex(
          this.$route,
          this.staleMenuFallbackIndex,
        )
      ) {
        return this.staleMenuFallbackIndex;
      }
      return "";
    },
  },
  watch: {
    currentMenuIndex(newVal) {
      if (newVal && this.$refs.menu) {
        this.$refs.menu.close(newVal);
      }
    },
    $route() {
      const d = this.resolveMenuActiveFromRoute(this.$route);
      if (d) {
        this.staleMenuFallbackIndex = d;
      } else if (
        !this.routeMatchesMenuIndex(
          this.$route,
          this.staleMenuFallbackIndex,
        )
      ) {
        this.staleMenuFallbackIndex = "";
      }
      this.$nextTick(() => this.syncElMenuHighlight());
    },
    routerList() {
      const d = this.resolveMenuActiveFromRoute(this.$route);
      if (d) this.staleMenuFallbackIndex = d;
      this.$nextTick(() => this.syncElMenuHighlight());
    },
  },
  mounted() {
    const initial = this.resolveMenuActiveFromRoute(this.$route);
    if (initial) this.staleMenuFallbackIndex = initial;
    this.$nextTick(() => this.syncElMenuHighlight());
  },
  methods: {
    ...mapActions("menuTag", ["addTag"]),
    toggleAllMenus() {
      this.isExpanded = !this.isExpanded;
      this.openMenus = this.isExpanded
        ? this.routerList
            .filter((item) => item.children && item.children.length)
            .map((item) => this.normalizedMenuParentIndex(item))
        : [];
    },
    normalizePath(path) {
      if (path == null || path === "") return "";
      const t = String(path).trim();
      return t.startsWith("/") ? t : `/${t}`;
    },
    /** 后台 path 常与 vue-router resolve 不一致，用多候选路径对齐 $route.path */
    menuPathCandidatesForLeaf(rawPath) {
      const p = this.normalizePath(rawPath);
      const set = new Set([p]);
      if (!/^\/home(\/|$)/.test(p)) {
        set.add(`/home${p.startsWith("/") ? "" : "/"}${p.replace(/^\//, "")}`);
      }
      return [...set];
    },
    leafMatchesRoute(leaf, routePath, qid) {
      const pathOk = this.menuPathCandidatesForLeaf(leaf.path).includes(
        routePath,
      );
      if (!pathOk) return false;
      if (qid === undefined) return true;
      return String(leaf.id) === qid;
    },
    parseMenuIndexString(indexStr) {
      if (!indexStr || typeof indexStr !== "string") return null;
      const q = indexStr.indexOf("?");
      const pathPart = q >= 0 ? indexStr.slice(0, q) : indexStr;
      let id = undefined;
      if (q >= 0) {
        const params = new URLSearchParams(indexStr.slice(q + 1));
        if (params.has("id")) id = String(params.get("id"));
      }
      const pathname = this.normalizePath(pathPart);
      return { pathname, id };
    },
    /** 当前路由是否与某个已注册的菜单 index（含 path + id）一致，用于避免因 path 写法差异误清高亮 */
    routeMatchesMenuIndex(route, indexStr) {
      const parsed = this.parseMenuIndexString(indexStr);
      if (!parsed) return false;
      const qid =
        route.query.id !== undefined ? String(route.query.id) : undefined;
      const candidates = this.menuPathCandidatesForLeaf(parsed.pathname);
      if (!candidates.includes(route.path)) return false;
      // 两边的 id 都有则必须一致；若 URL 暂时没有 id（重定向瞬时等），只要 path 对上就视为仍为该菜单页
      if (parsed.id !== undefined && qid !== undefined) {
        return parsed.id === qid;
      }
      return true;
    },
    /** 必须与 el-menu-item 的 :index 字符串完全一致（含路径规范），否则高亮失效 */
    menuItemIndex(item) {
      const p = this.normalizePath(item.path);
      return `${p}?id=${item.id}`;
    },
    normalizedMenuParentIndex(item) {
      return this.normalizePath(item.path);
    },
    /** 与 menuDefaultActive 一致即选中（兜底子菜单叶子项未及时打上 is-active 时仍显示蓝底高亮） */
    menuLeafIsActive(item) {
      if (!item) return false;
      const idx = this.menuDefaultActive;
      return !!idx && idx === this.menuItemIndex(item);
    },
    getVisibleLeafMenuItems() {
      const leaves = [];
      for (const item of this.routerList) {
        if (item.meta?.hidden !== false) continue;
        const isSubmenuParent =
          item.children &&
          item.children.length &&
          item.meta?.hidden === false;
        if (isSubmenuParent) {
          for (const child of item.children) {
            if (child.meta?.hidden === false) {
              leaves.push(child);
            }
          }
        } else {
          leaves.push(item);
        }
      }
      return leaves;
    },
    resolveMenuActiveFromRoute(route) {
      const routePath = route.path;
      const qid =
        route.query.id !== undefined ? String(route.query.id) : undefined;
      const leaves = this.getVisibleLeafMenuItems();

      let chosen = null;
      const pathMatches = leaves.filter((leaf) =>
        this.leafMatchesRoute(leaf, routePath, qid),
      );

      if (pathMatches.length === 1) {
        chosen =
          qid !== undefined &&
          String(pathMatches[0].id) !== qid
            ? null
            : pathMatches[0];
      } else if (pathMatches.length > 1) {
        chosen =
          qid !== undefined
            ? pathMatches.find((leaf) => String(leaf.id) === qid) || null
            : null;
      }

      let index = chosen ? this.menuItemIndex(chosen) : "";

      /** 兜底：pathname 已对但菜单 index 仅用「无 /home 前缀」等写法注册了 items */
      if (!index && leaves.length && qid !== undefined) {
        const idLeaf = leaves.find((leaf) => String(leaf.id) === qid);
        if (
          idLeaf &&
          this.menuPathCandidatesForLeaf(idLeaf.path).includes(routePath)
        ) {
          index = this.menuItemIndex(idLeaf);
        }
      }

      return index;
    },
    syncElMenuHighlight(retriesLeft = 16) {
      const menu = this.$refs.menu;
      if (!menu) return;

      const idx = this.menuDefaultActive;
      const items = menu.items || {};
      const current = menu.activeIndex;

      if (!idx) {
        if (
          current &&
          items[current] &&
          this.routeMatchesMenuIndex(this.$route, current)
        ) {
          return;
        }
        menu.activeIndex = "";
        return;
      }

      if (!items[idx]) {
        if (retriesLeft > 0) {
          this.$nextTick(() =>
            this.syncElMenuHighlight(retriesLeft - 1),
          );
        } else if (
          current &&
          items[current] &&
          this.routeMatchesMenuIndex(this.$route, current)
        ) {
          return;
        }
        return;
      }

      menu.activeIndex = idx;
      if (idx) this.staleMenuFallbackIndex = idx;
      menu.initOpenedMenu();
    },
    // 修复后的跳转方法
    handleChangeMenuUrl(item) {
      const key = this.menuItemIndex(item);
      this.staleMenuFallbackIndex = key;

      const tag = { path: key, name: item.name, label: item.name };
      this.addTag(tag);

      // 用 nextTick 兼容 Firefox
      this.$nextTick(() => {
        this.$router.push(key).catch((err) => {
          if (err.name !== "NavigationDuplicated") {
            console.error("路由跳转失败:", err);
          }
        });
      });
    },
    isElPrefix(str) {
      return /^el-/.test(str);
    },
    handleOpen(key, keyPath) {
      this.activeIndex = false;
      this.keyObject = { key, keyPath };
    },
    handleClose(key, keyPath) {
      this.activeIndex = false;
      this.keyObject = { key, keyPath };
    },
  },
};
</script>

<style lang="scss" scoped>
/* 深色侧栏主题色 */
$dt-bg: #141414;
$dt-item-blue: #1890ff;
$dt-active-bg: #1890ff;
$dt-text-inactive: #999999;
$dt-radius: 8px;

.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
.stop-animation,
.active-animation {
  font-size: 0px;
}

.asideBox {
  height: 100vh;
  overflow-y: scroll;
  position: relative;
  padding-bottom: 40px;
  padding-top: 16px;
  box-sizing: border-box;
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  flex-direction: column;
  position: relative;

  &.asideBox--tech {
    background: $dt-bg;
    border-right: 1px solid #2a2a2a;
    border-radius: 0 16px 16px 0;

    .el-menu {
      background-color: $dt-bg !important;
    }

    .foldBox .icon {
      color: #a6a6a6;
    }
  }

  &.asideBox--legacy {
    background: #eff1f3;
    border-right: 1px solid #e4e7ed;
    border-radius: 0;

    .el-menu {
      background-color: #eff1f3 !important;
    }

    .foldBox .icon {
      color: #666666;
    }
  }

  .homeBox {
    width: 100%;
    padding: 0 20px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    box-sizing: border-box;

    .logoItem {
      display: flex;
      width: 100%;
      cursor: pointer;
      box-sizing: border-box;

      span {
        line-height: 50px;
        margin: 0 auto;
      }

      .logoImg {
        img {
          height: 100%;
          width: inherit;
        }

        width: 50px;
        height: 50px;
      }
    }

    .search {
      display: flex;
      width: 100%;
      align-items: center;
      justify-content: center;

      .el-icon-search {
        margin-top: 28px;
      }

      .el-input {
        width: inherit;
      }
    }

    .frame {
      display: flex;
      justify-content: space-between;
      align-items: center;
      color: #666;
      width: 100%;
      margin-top: 20px;
      margin-bottom: 10px;
      cursor: pointer;

      .homeIndex {
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 18px;

        .home_active {
          margin-right: 2px;
        }

        p {
          color: #666;
          font-size: 18px;
        }
      }

      &:hover {
        p {
          color: #19436d;
        }

        transition: width 0.5s ease, transform 0.5s ease;
        transform-origin: left;
      }

      .left {
        left: 50%;
        transform: scaleX(0);
      }

      .right {
        left: 0;
        transform: scaleX(0);
      }
    }
  }

  .el-menu {
    margin-bottom: 10px;
    border: none;
    overflow-y: auto;
    width: 100% !important;
    height: 100%;
  }
}

::v-deep .menu-dt-light.el-menu {
  .el-menu-item,
  .el-submenu__title {
    color: #606266 !important;
    border-radius: 4px;
    margin: 2px 8px;
    height: 44px;
    line-height: 44px;
  }

  .el-menu-item:not(.is-active):not(.dt-menu-leaf-active) .svnIcon,
  .el-submenu__title .svnIcon {
    color: #606266;
    svg {
      fill: #606266;
    }
  }

  .el-menu-item.dt-menu-leaf-active,
  .el-menu-item.is-active {
    background-color: transparent !important;
    color: var(--primary-color, #82780c) !important;

    span {
      color: var(--primary-color, #82780c) !important;
    }

    .el-icon-menu,
    .svnIcon {
      color: var(--primary-color, #82780c) !important;
    }

    .svnIcon svg {
      fill: var(--primary-color, #82780c) !important;
    }
  }

  .el-menu-item:hover:not(.is-active):not(.dt-menu-leaf-active),
  .el-submenu__title:hover {
    background-color: rgba(0, 0, 0, 0.04) !important;
  }

  .el-menu--inline {
    background-color: #eff1f3 !important;
  }

  .el-submenu .el-menu-item {
    padding-left: 44px !important;
  }
}

.flexCenter {
  display: flex;
  align-items: center !important;
  justify-content: space-between !important;
  width: 200px;
}

.foldBox {
  position: fixed;
  bottom: 10px;
  left: 20px;
  padding: 10px;

  .icon {
    cursor: pointer;
    color: #a6a6a6;
    font-size: 14px;
  }
}

::v-deep .menu-dt-dark.el-menu {
  .el-menu-item,
  .el-submenu__title {
    color: $dt-text-inactive !important;
    border-radius: $dt-radius;
    margin: 4px 10px;
    margin-left: 10px !important;
    height: 44px;
    line-height: 44px;
    border: none !important;
  }

  /* 未选中：字灰、图标品牌蓝 */
  .el-menu-item:not(.is-active):not(.dt-menu-leaf-active) .el-icon-menu,
  .el-submenu__title .el-icon-menu {
    color: $dt-item-blue !important;
  }

  .el-menu-item:not(.is-active):not(.dt-menu-leaf-active) span,
  .el-submenu__title span {
    color: $dt-text-inactive !important;
  }

  .el-menu-item:not(.is-active):not(.dt-menu-leaf-active) .svnIcon,
  .el-submenu__title .svnIcon {
    color: $dt-item-blue;
    svg {
      fill: $dt-item-blue;
    }
  }

  /* Element is-active + 路由类：蓝底白字（子菜单内需盖过 theme-chalk hover 底色）*/
  .el-menu-item.dt-menu-leaf-active,
  .el-menu-item.is-active {
    background-color: $dt-active-bg !important;
    color: #fff !important;

    span {
      color: #fff !important;
    }

    .el-icon-menu,
    .svnIcon {
      color: #fff !important;
    }
    .svnIcon svg {
      fill: #fff !important;
    }
  }

  .el-submenu .el-menu--inline .el-menu-item.dt-menu-leaf-active,
  .el-submenu .el-menu--inline .el-menu-item.is-active {
    background-color: $dt-active-bg !important;
  }

  .el-menu-item.dt-menu-leaf-active:hover,
  .el-menu-item.dt-menu-leaf-active:focus,
  .el-menu-item.is-active:hover,
  .el-menu-item.is-active:focus {
    background-color: $dt-active-bg !important;
    color: #fff !important;
  }

  .el-menu-item:focus,
  .el-menu-item:hover:not(.is-active):not(.dt-menu-leaf-active) {
    background-color: rgba(255, 255, 255, 0.06) !important;
  }

  .el-submenu__title:hover {
    background-color: rgba(255, 255, 255, 0.06) !important;
  }

  /* 子菜单展开区域 */
  .el-menu--inline {
    background-color: #0d0d0d !important;
  }

  .el-submenu .el-menu-item {
    min-width: auto;
    padding-left: 44px !important;
  }
}

::v-deep .el-submenu__title {
  display: flex;
  align-items: center;
}

::v-deep .el-submenu__icon-arrow {
  right: 16px;
  color: $dt-text-inactive;
}

::v-deep .svnIcon {
  vertical-align: middle;
  margin-right: 8px;
  width: 24px;
  text-align: center;
  font-size: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

::-webkit-scrollbar {
  width: 6px;
  display: none;
}

::-webkit-scrollbar-thumb {
  border-radius: 10px;
  background-color: #3a3a3a;
  cursor: pointer;
}

::-webkit-scrollbar-thumb:hover {
  background-color: #4a4a4a;
}

::-webkit-scrollbar-track-piece {
  background-color: #1a1a1a;
  border-radius: 10px;
}

.asideBox:hover ::-webkit-scrollbar {
  display: block !important;
}

/* 旧版侧栏左缩进由 ::v-deep 内 margin 统一控制 */
</style>

<!-- 收起侧栏时子菜单弹出层挂到 body，需非 scoped 样式 -->
<style lang="scss">
$dt-bg: #141414;
$dt-item-blue: #1890ff;
$dt-active-bg: #1890ff;
$dt-text-inactive: #999999;
$dt-radius: 8px;

.menu-dt-dark-popup {
  .el-menu--popup {
    background-color: $dt-bg !important;
    border: 1px solid rgba(0, 170, 255, 0.25);
    min-width: 160px;
    padding: 4px 0;
  }

  .el-menu-item {
    color: $dt-text-inactive !important;
    border-radius: $dt-radius;
    margin: 4px 8px;
    height: 44px;
    line-height: 44px;

    span {
      color: $dt-text-inactive !important;
    }

    .el-icon-menu,
    .svnIcon {
      color: $dt-item-blue !important;
    }

    .svnIcon svg {
      fill: $dt-item-blue;
    }
  }

  .el-menu-item.is-active,
  .el-menu-item.dt-menu-leaf-active {
    background-color: $dt-active-bg !important;
    color: #fff !important;

    span {
      color: #fff !important;
    }

    .el-icon-menu,
    .svnIcon {
      color: #fff !important;
    }

    .svnIcon svg {
      fill: #fff !important;
    }
  }

  .el-menu-item.is-active:hover,
  .el-menu-item.is-active:focus,
  .el-menu-item.dt-menu-leaf-active:hover,
  .el-menu-item.dt-menu-leaf-active:focus {
    background-color: $dt-active-bg !important;
    color: #fff !important;

    span {
      color: #fff !important;
    }
  }

  .el-menu-item:hover:not(.is-active):not(.dt-menu-leaf-active) {
    background-color: rgba(255, 255, 255, 0.06) !important;
  }
}
</style>
