SsSubTab组件改造方案.md 13 KB

SsSubTab 组件改造方案

文件位置

/js/vue/ss-components.js 第 5715-5895 行

改造目标

1. UI 模式改造

  • 去掉:顶部图片 headerImage
  • 新增两种模式
    • collapse(默认):只显示图标,鼠标悬停时浮动显示文字
    • fixed:只显示图标,不展开浮动

2. 性能优化

  • 现状:iframe 用 v-show,页面打开时全部加载
  • 改为:懒加载,点击哪个才加载哪个

具体修改方案

一、Props 修改

// 删除
headerImage: { type: String, default: "" },

// 新增
initialMode: {
  type: String,
  default: 'collapse',  // 'collapse' | 'fixed'
  validator: (v) => ['collapse', 'fixed'].includes(v)
},

二、Setup 新增逻辑

setup(props, { emit }) {
  // ... 原有逻辑保留 ...

  // ===== 新增:菜单模式管理 =====
  const menuMode = ref(props.initialMode); // 'collapse' | 'fixed'
  const isHovering = ref(false);  // 鼠标是否在菜单区域

  // 切换模式
  const toggleMenuMode = () => {
    menuMode.value = menuMode.value === 'collapse' ? 'fixed' : 'collapse';
  };

  // 鼠标进入/离开
  const onMouseEnter = () => {
    if (menuMode.value === 'collapse') {
      isHovering.value = true;
    }
  };
  const onMouseLeave = () => {
    isHovering.value = false;
  };

  // 是否显示文字(展开状态)
  const isExpanded = computed(() => {
    return menuMode.value === 'collapse' && isHovering.value;
  });

  // ===== 新增:iframe 懒加载 =====
  const loadedMenus = ref(new Set());  // 已加载过的菜单 name

  // 修改 selectItem
  const selectItem = (item) => {
    currentMenu.value = item;
    // 标记为已加载
    if (item.name) {
      loadedMenus.value.add(item.name);
    }
    emit("menu-change", item);
  };

  // 初始化:默认选中项需要加入已加载集合
  watch(currentMenu, (menu) => {
    if (menu?.name) {
      loadedMenus.value.add(menu.name);
    }
  }, { immediate: true });

  // 判断菜单是否已加载
  const isMenuLoaded = (menuName) => {
    return loadedMenus.value.has(menuName);
  };

  return {
    // 原有
    currentMenu,
    selectItem,
    handleFooterClick,
    // 新增
    menuMode,
    isHovering,
    isExpanded,
    toggleMenuMode,
    onMouseEnter,
    onMouseLeave,
    isMenuLoaded,
  };
}

三、Template 修改

<div class="project-edit-container">
    <!-- 左侧菜单 -->
    <div class="left-side"
         v-if="leftDisplay"
         :data-mode="menuMode"
         :class="{ 'is-expanded': isExpanded }"
         @mouseenter="onMouseEnter"
         @mouseleave="onMouseLeave">

        <!-- 删除:头部图片 -->

        <!-- 菜单内容 -->
        <div class="menu-content">
            <div class="scroll-view">
                <template v-for="(menuItem, i) in menuList" :key="i">

                    <!-- 分组菜单 -->
                    <div v-if="menuItem.children?.length > 0" class="group">
                        <div class="menu-item" @click="menuItem.open = !menuItem.open">
                            <ss-icon :name="menuItem.icon || 'folder'" class="menu-icon" size="20px" />
                            <span class="menu-label" v-show="isExpanded">{{ menuItem.title }}</span>
                            <span class="arrow" v-show="isExpanded">
                                <ss-icon :name="menuItem.open ? 'arrow-up' : 'arrow-down'" size="16px" />
                            </span>
                            <!-- 收起时的悬浮提示 -->
                            <div class="menu-tooltip" v-show="!isExpanded">{{ menuItem.title }}</div>
                        </div>
                        <div v-show="menuItem.open && isExpanded" class="group-detail">
                            <div v-for="(item, j) in menuItem.children"
                                :key="j"
                                class="menu-item"
                                :class="{ active: item.name === currentMenu?.name }"
                                @click="selectItem(item)">
                                <ss-icon :name="item.icon || 'file'" class="menu-icon" size="18px" />
                                <span class="menu-label">{{ item.title }}</span>
                                <span class="menu-item-point" v-if="item.cgxList || item.objectList"></span>
                            </div>
                        </div>
                    </div>

                    <!-- 普通菜单项 -->
                    <div v-else
                        class="menu-item"
                        :class="{ active: menuItem.name === currentMenu?.name }"
                        @click="selectItem(menuItem)">
                        <ss-icon :name="menuItem.icon || 'file'" class="menu-icon" size="20px" />
                        <span class="menu-label" v-show="isExpanded">{{ menuItem.title }}</span>
                        <span class="menu-item-point" v-if="menuItem.cgxList || menuItem.objectList"></span>
                        <!-- 收起时的悬浮提示 -->
                        <div class="menu-tooltip" v-show="!isExpanded">{{ menuItem.title }}</div>
                    </div>

                </template>
            </div>
        </div>

        <!-- 底部:模式切换按钮 -->
        <div class="menu-mode-toggle" @click="toggleMenuMode">
            <ss-icon :name="menuMode === 'collapse' ? 'pin' : 'pin-fill'" size="20px" />
            <span class="menu-label" v-show="isExpanded">
                {{ menuMode === 'collapse' ? '固定菜单' : '取消固定' }}
            </span>
        </div>

        <!-- 底部按钮(保留原有) -->
        <div v-if="footerButtons.length > 0" class="sub-tab-menu-footer" @click="footerButtons[0].onclick">
            <!-- ... 保持原有 ... -->
        </div>

    </div>

    <!-- 右侧内容区域 - 懒加载 iframe -->
    <div class="content-area fit-height-content"
         style="overflow: hidden;"
         :style="!leftDisplay ? { width: '100%' } : {}">

        <template v-for="(menuItem, i) in menuList" :key="i">
            <!-- 只有加载过的才渲染 iframe -->
            <iframe
                v-if="isMenuLoaded(menuItem.name)"
                :src="menuItem.url"
                style="height: 100%;width: 100%;"
                frameborder="0"
                class="sub-tab-iframe"
                :id="i === 0 ? 'sub-tab-iframe' : ''"
                v-show="currentMenu?.name === menuItem.name"
            />
        </template>

    </div>
</div>

CSS 样式修改(base.css:4784-4968)

现有样式分析

/* 现有:固定 180px 宽度 */
.project-edit-container .left-side {
  width: 180px !important;  /* 需要改为动态 */
}
.project-edit-container>div.content-area {
  width: calc(100% - 180px);  /* 需要改为动态 */
}

需要修改的样式

1. 左侧容器宽度(行 4798-4802)

/* 原来 */
.project-edit-container .left-side {
  width: 180px !important;
  border-right: 1px solid #e2e4ec;
  background-color: #edf1f5;
}

/* 改为 */
.project-edit-container .left-side {
  width: 60px;  /* 默认收起宽度 */
  border-right: 1px solid #e2e4ec;
  background-color: #edf1f5;
  transition: width 0.2s ease;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* 展开状态 */
.project-edit-container .left-side.is-expanded {
  width: 200px;
}

/* 固定模式:始终收起,不响应悬浮 */
.project-edit-container .left-side[data-mode="fixed"] {
  width: 60px !important;
}

2. 右侧内容区宽度(行 4964-4967)

/* 原来 */
.project-edit-container>div.content-area {
  width: calc(100% - 180px);
  overflow-y: auto;
}

/* 改为 */
.project-edit-container>div.content-area {
  flex: 1;  /* 自动填充剩余空间 */
  overflow-y: auto;
}

3. 菜单项样式(行 4824-4832)

/* 原来 */
.project-edit-container .menu-item,
.project-edit-container .group .menu-item {
  padding: 20px 12px 20px 30px;
  cursor: pointer;
  position: relative;
  color: #333333;
  display: flex;
  align-items: center;
}

/* 改为 */
.project-edit-container .menu-item,
.project-edit-container .group .menu-item {
  padding: 15px;
  cursor: pointer;
  position: relative;
  color: #333333;
  display: flex;
  align-items: center;
  justify-content: center;  /* 收起时图标居中 */
  gap: 10px;
}

/* 展开时左对齐 */
.project-edit-container .left-side.is-expanded .menu-item {
  justify-content: flex-start;
  padding: 15px 20px;
}

4. 删除头部图片相关样式(行 4808-4815)

/* 删除或注释掉 */
/* .project-edit-container .menu-header {
  height: 120px;
  border-bottom: 1px solid #d8dae3;
} */

/* 修改 menu-content 高度 */
.project-edit-container .menu-content {
  height: calc(100% - 50px);  /* 原来是 calc(100% - 60px),去掉图片后调整 */
  flex: 1;
  overflow: hidden;
}

5. 箭头位置(行 4834-4837)

/* 原来:固定位置 */
.project-edit-container .menu-item .arrow {
  position: absolute;
  left: 180px;
}

/* 改为:相对定位,放在右侧 */
.project-edit-container .menu-item .arrow {
  margin-left: auto;
}

需要新增的样式

/* ===== 新增:图标样式 ===== */
.project-edit-container .menu-icon {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ===== 新增:文字标签 ===== */
.project-edit-container .menu-label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 收起时隐藏文字 */
.project-edit-container .left-side:not(.is-expanded) .menu-label {
  display: none;
}

/* ===== 新增:悬浮提示(收起时显示) ===== */
.project-edit-container .menu-tooltip {
  position: absolute;
  left: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%);
  background: #393d51;
  color: #fff;
  padding: 8px 16px;
  border-radius: 4px;
  white-space: nowrap;
  z-index: 1000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
  font-size: 14px;
}

/* 小三角 */
.project-edit-container .menu-tooltip::before {
  content: "";
  position: absolute;
  left: -6px;
  top: 50%;
  transform: translateY(-50%);
  border: 6px solid transparent;
  border-right-color: #393d51;
  border-left: none;
}

/* 悬浮显示 tooltip */
.project-edit-container .left-side:not(.is-expanded) .menu-item:hover .menu-tooltip {
  opacity: 1;
}

/* 展开时隐藏 tooltip */
.project-edit-container .left-side.is-expanded .menu-tooltip {
  display: none;
}

/* ===== 新增:模式切换按钮 ===== */
.project-edit-container .menu-mode-toggle {
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 15px;
  cursor: pointer;
  border-top: 1px solid #d8dae3;
  color: #666;
  gap: 10px;
  flex-shrink: 0;
}

.project-edit-container .menu-mode-toggle:hover {
  background: #e0e4ea;
  color: #333;
}

/* 展开时左对齐 */
.project-edit-container .left-side.is-expanded .menu-mode-toggle {
  justify-content: flex-start;
  padding: 15px 20px;
}

/* ===== 新增:子菜单样式调整 ===== */
.project-edit-container .group-detail {
  background: rgba(0, 0, 0, 0.03);
}

.project-edit-container .group-detail .menu-item {
  padding-left: 25px;
}

.project-edit-container .left-side.is-expanded .group-detail .menu-item {
  padding-left: 50px;
}

/* 收起时隐藏子菜单 */
.project-edit-container .left-side:not(.is-expanded) .group-detail {
  display: none;
}

CSS 修改汇总表

行号 原值 新值 说明
4799 width: 180px !important width: 60px 默认收起宽度
4808-4811 .menu-header {...} 删除或注释 去掉头部图片
4814 height: calc(100% - 60px) flex: 1 菜单内容自适应
4826 padding: 20px 12px 20px 30px padding: 15px 菜单项内边距
4836 left: 180px margin-left: auto 箭头位置
4965 width: calc(100% - 180px) flex: 1 右侧内容区自适应

数据结构要求

菜单项需要增加 icon 字段:

menuList: [
  {
    name: 'basic',
    title: '基本信息',
    icon: 'info',      // 新增:图标名称
    url: '/page/xxx.jsp'
  },
  {
    title: '项目设置',
    icon: 'setting',   // 新增
    children: [
      { name: 'config1', title: '配置1', icon: 'config', url: '...' },
      { name: 'config2', title: '配置2', icon: 'config', url: '...' }
    ]
  }
]

改造要点总结

项目 原实现 新实现
头部图片 有 headerImage 删除
菜单宽度 固定宽度 60px / 200px 切换
文字显示 始终显示 收起时隐藏,悬浮/展开时显示
模式切换 collapse(悬浮展开) / fixed(始终收起)
iframe 加载 v-show 全部加载 v-if 懒加载(点击才加载)
图标 每个菜单项需要 icon 字段

兼容性注意

  1. 需要确保 menuList 数据有 icon 字段,否则使用默认图标
  2. 懒加载后,切换回已加载的菜单不会重新加载(保持 iframe 状态)
  3. 底部按钮功能保持不变