# SsSubTab 组件改造方案
## 文件位置
`/js/vue/ss-components.js` 第 5715-5895 行
## 改造目标
### 1. UI 模式改造
- **去掉**:顶部图片 `headerImage`
- **新增两种模式**:
- `collapse`(默认):只显示图标,鼠标悬停时浮动显示文字
- `fixed`:只显示图标,不展开浮动
### 2. 性能优化
- **现状**:iframe 用 `v-show`,页面打开时全部加载
- **改为**:懒加载,点击哪个才加载哪个
---
## 具体修改方案
### 一、Props 修改
```js
// 删除
headerImage: { type: String, default: "" },
// 新增
initialMode: {
type: String,
default: 'collapse', // 'collapse' | 'fixed'
validator: (v) => ['collapse', 'fixed'].includes(v)
},
```
### 二、Setup 新增逻辑
```js
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 修改
```html
```
---
## CSS 样式修改(base.css:4784-4968)
### 现有样式分析
```css
/* 现有:固定 180px 宽度 */
.project-edit-container .left-side {
width: 180px !important; /* 需要改为动态 */
}
.project-edit-container>div.content-area {
width: calc(100% - 180px); /* 需要改为动态 */
}
```
### 需要修改的样式
#### 1. 左侧容器宽度(行 4798-4802)
```css
/* 原来 */
.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)
```css
/* 原来 */
.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)
```css
/* 原来 */
.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)
```css
/* 删除或注释掉 */
/* .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)
```css
/* 原来:固定位置 */
.project-edit-container .menu-item .arrow {
position: absolute;
left: 180px;
}
/* 改为:相对定位,放在右侧 */
.project-edit-container .menu-item .arrow {
margin-left: auto;
}
```
### 需要新增的样式
```css
/* ===== 新增:图标样式 ===== */
.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` 字段:
```js
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. 底部按钮功能保持不变