240 lines
6.9 KiB
Markdown
240 lines
6.9 KiB
Markdown
# 设置面板 UI 改进总结
|
||
|
||
## 问题修复
|
||
|
||
### 1. 保存后未立即生效 ✅
|
||
|
||
**原因分析:**
|
||
- `tabs` 数组中的 `visible` 属性与 `visibleTabs` 数组不同步
|
||
- `watch(selectedTabs)` 覆盖了拖拽排序的顺序
|
||
|
||
**修复方案:**
|
||
1. 在 `handleTabVisibilityChange` 中同步更新 `tabs` 数组的 `visible` 属性
|
||
2. 移除 `watch(selectedTabs)`,避免覆盖排序
|
||
3. 在 `handleSave` 中确保数据完全同步
|
||
4. 在 `loadConfig` 中从后端加载时同步 `visible` 属性
|
||
|
||
### 2. UI 合并优化 ✅
|
||
|
||
**改进前:**
|
||
- Tab 显示、默认 Tab、拖拽排序分成三个独立的卡片
|
||
- 用户需要在不同区域操作,不够直观
|
||
|
||
**改进后:**
|
||
- 统一到一个列表中,每行包含所有控制项
|
||
- 更直观、更高效的配置体验
|
||
|
||
## 新的 UI 设计
|
||
|
||
### Tab 配置列表结构
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ ℹ️ 拖拽可调整 Tab 顺序,勾选复选框控制显示,单选按钮 │
|
||
│ 设置默认打开的 Tab │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ ⋮ ☑ 数据库 ○ 默认打开 │
|
||
│ ⋮ ☑ 文件管理 ○ 默认打开 │
|
||
│ ⋮ ☑ 设备调用测试 ⦿ 默认打开 │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ 隐藏的 Tabs │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ ⋮ ☐ (其他隐藏的 Tab) │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 每行包含的元素
|
||
|
||
1. **拖拽手柄** (⋮) - 拖拽调整顺序
|
||
2. **复选框** (☑/☐) - 控制显示/隐藏
|
||
3. **Tab 标题** - 显示 Tab 名称
|
||
4. **单选按钮** (⦿) - 设置默认打开的 Tab
|
||
|
||
## 技术实现
|
||
|
||
### 新增辅助函数
|
||
|
||
```javascript
|
||
// 判断 Tab 是否可见
|
||
const isTabVisible = (key) => {
|
||
return localConfig.value.visibleTabs.includes(key)
|
||
}
|
||
|
||
// 判断 Tab 是否启用
|
||
const isTabEnabled = (key) => {
|
||
const tab = localConfig.value.tabs.find(t => t.key === key)
|
||
return tab ? tab.enabled : false
|
||
}
|
||
|
||
// 隐藏的 Tabs 计算属性
|
||
const hiddenTabs = computed(() => {
|
||
return localConfig.value.tabs.filter(
|
||
tab => !localConfig.value.visibleTabs.includes(tab.key)
|
||
)
|
||
})
|
||
```
|
||
|
||
### 改进的可见性处理
|
||
|
||
```javascript
|
||
// 处理单个 Tab 可见性变化
|
||
const handleTabVisibilityChange = (tabKey, visible) => {
|
||
if (visible) {
|
||
// 显示 Tab:添加到 visibleTabs 末尾
|
||
localConfig.value.visibleTabs.push(tabKey)
|
||
} else {
|
||
// 隐藏 Tab:从 visibleTabs 中移除
|
||
// 至少保留一个 Tab
|
||
if (localConfig.value.visibleTabs.length <= 1) {
|
||
Message.warning('至少需要保留一个可见的 Tab')
|
||
return
|
||
}
|
||
|
||
// 如果隐藏的是默认 Tab,需要更改默认 Tab
|
||
if (localConfig.value.defaultTab === tabKey) {
|
||
const remainingTabs = localConfig.value.visibleTabs.filter(k => k !== tabKey)
|
||
localConfig.value.defaultTab = remainingTabs[0] || ''
|
||
}
|
||
|
||
localConfig.value.visibleTabs = localConfig.value.visibleTabs.filter(
|
||
k => k !== tabKey
|
||
)
|
||
}
|
||
|
||
// 同步更新 tabs 数组中的 visible 属性
|
||
localConfig.value.tabs = localConfig.value.tabs.map(tab => ({
|
||
...tab,
|
||
visible: localConfig.value.visibleTabs.includes(tab.key)
|
||
}))
|
||
}
|
||
```
|
||
|
||
### 保存前数据同步
|
||
|
||
```javascript
|
||
// 保存配置
|
||
const handleSave = async () => {
|
||
// ... 验证逻辑 ...
|
||
|
||
// 确保 tabs 数组中的 visible 属性与 visibleTabs 完全同步
|
||
const syncedTabs = localConfig.value.tabs.map(tab => ({
|
||
...tab,
|
||
visible: localConfig.value.visibleTabs.includes(tab.key)
|
||
}))
|
||
|
||
const configToSave = {
|
||
tabs: syncedTabs,
|
||
visibleTabs: [...localConfig.value.visibleTabs],
|
||
defaultTab: localConfig.value.defaultTab
|
||
}
|
||
|
||
// ... 保存逻辑 ...
|
||
}
|
||
```
|
||
|
||
## 样式改进
|
||
|
||
### 配置项样式
|
||
|
||
```css
|
||
.tab-config-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px 16px;
|
||
background: var(--color-fill-1);
|
||
border: 1px solid var(--color-border);
|
||
border-radius: 6px;
|
||
cursor: move;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.tab-config-item:hover {
|
||
background: var(--color-fill-2);
|
||
border-color: var(--color-border-2);
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.tab-config-item.dragging {
|
||
opacity: 0.5;
|
||
background: var(--color-fill-2);
|
||
transform: scale(0.98);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||
}
|
||
```
|
||
|
||
### 隐藏项样式
|
||
|
||
```css
|
||
.tab-config-item.hidden {
|
||
opacity: 0.6;
|
||
cursor: default;
|
||
}
|
||
|
||
.tab-config-item.hidden:hover {
|
||
border-color: var(--color-border);
|
||
box-shadow: none;
|
||
}
|
||
```
|
||
|
||
## 用户体验改进
|
||
|
||
1. **一目了然** - 所有配置集中在一行,无需来回查看
|
||
2. **即时反馈** - 拖拽时有视觉反馈(透明度、缩放、阴影)
|
||
3. **智能提示** - 顶部说明文字告知用户如何操作
|
||
4. **分组显示** - 可见和隐藏的 Tab 分开展示
|
||
5. **保护机制** - 至少保留一个可见 Tab,最后一个 Tab 的复选框禁用
|
||
|
||
## 数据流保证
|
||
|
||
### 配置加载
|
||
```
|
||
后端 → GetAppConfig → loadConfig → 同步 visible 属性 → 显示
|
||
```
|
||
|
||
### 配置保存
|
||
```
|
||
用户操作 → localConfig → handleSave → 同步数据 → 后端保存 → 刷新 UI
|
||
```
|
||
|
||
### 关键同步点
|
||
|
||
1. **复选框改变** → 同步 `visibleTabs` 和 `tabs[].visible`
|
||
2. **拖拽排序** → 更新 `visibleTabs` 顺序
|
||
3. **保存前** → 确保所有 `visible` 属性与 `visibleTabs` 一致
|
||
4. **加载后** → 根据后端数据同步 `visible` 属性
|
||
|
||
## 测试清单
|
||
|
||
### 基础功能
|
||
- ✅ 拖拽排序 Tab
|
||
- ✅ 勾选/取消勾选复选框
|
||
- ✅ 设置默认 Tab
|
||
- ✅ 保存配置后立即生效
|
||
- ✅ 刷新页面后配置保持
|
||
|
||
### 边界情况
|
||
- ✅ 至少保留一个可见 Tab
|
||
- ✅ 隐藏默认 Tab 时自动切换
|
||
- ✅ 禁用的 Tab 不可操作
|
||
- ✅ 配置为空时显示默认值
|
||
|
||
### UI 交互
|
||
- ✅ 拖拽时视觉反馈
|
||
- ✅ hover 状态提示
|
||
- ✅ 隐藏项分组显示
|
||
- ✅ 说明文字清晰
|
||
|
||
## 修复文件
|
||
|
||
- `frontend/src/components/SettingsPanel.vue` - UI 合并和逻辑修复
|
||
- `frontend/src/App.vue` - loadConfig 和 handleSaveConfig 数据同步
|
||
|
||
## 总结
|
||
|
||
本次改进解决了两个主要问题:
|
||
1. **保存后未立即生效** - 通过数据同步机制修复
|
||
2. **UI 不够直观** - 通过统一配置列表优化
|
||
|
||
用户体验得到显著提升,配置操作更加高效和直观。
|