351 lines
9.2 KiB
Markdown
351 lines
9.2 KiB
Markdown
# 架构迁移完成指南 - 事件驱动架构
|
||
|
||
## 当前状态
|
||
|
||
已创建以下新文件:
|
||
|
||
1. **`frontend/src/views/db-cli/composables/useEventBus.ts`** - 事件总线
|
||
- 类型安全的事件定义
|
||
- 支持事件订阅/取消/触发
|
||
- 自动错误处理和日志
|
||
|
||
2. **`frontend/src/views/db-cli/composables/useStructureStore.ts`** - 新的表结构 Store
|
||
- 单例模式,全局共享状态
|
||
- 事件驱动的状态更新
|
||
- 清晰的日志追踪
|
||
|
||
3. **`frontend/src/views/db-cli/composables/useStructureStoreLegacy.ts`** - 旧版本(已重命名)
|
||
- 原 `useStructureState.ts` 的副本
|
||
- 保留用于兼容和参考
|
||
|
||
4. **`frontend/src/views/db-cli/composables/MIGRATION.md`** - 迁移文档
|
||
- 详细的对表和迁移步骤
|
||
- 使用示例和注意事项
|
||
|
||
## 手动完成迁移步骤
|
||
|
||
### 步骤 1:修改 `index.vue` 的导入
|
||
|
||
**位置**:`frontend/src/views/db-cli/index.vue` 第 120 行
|
||
|
||
**原代码**:
|
||
```typescript
|
||
import { useStructureState } from './composables/useStructureState'
|
||
```
|
||
|
||
**修改为**:
|
||
```typescript
|
||
import { useStructureStore } from './composables/useStructureStore'
|
||
```
|
||
|
||
---
|
||
|
||
### 步骤 2:替换状态初始化(第 166-219 行)
|
||
|
||
**原代码**(删除第 166-219 行):
|
||
```typescript
|
||
const structureState = useStructureState()
|
||
const {
|
||
structureLoading,
|
||
structureError,
|
||
structureData,
|
||
structureInfo,
|
||
loadStructure,
|
||
clearStructure,
|
||
refreshStructure
|
||
} = structureState
|
||
|
||
// 使用计算属性确保响应式传递到子组件
|
||
const computedStructureLoading = computed(() => {
|
||
const val = structureState.structureLoading.value
|
||
console.log('🔵 computedStructureLoading 计算:', val)
|
||
return val
|
||
})
|
||
const computedStructureError = computed(() => {
|
||
const val = structureState.structureError.value
|
||
console.log('🔵 computedStructureError 计算:', val)
|
||
return val
|
||
})
|
||
const computedStructureData = computed(() => {
|
||
const val = structureState.structureData.value
|
||
console.log('🔵 computedStructureData 计算:', val)
|
||
return val
|
||
})
|
||
const computedStructureInfo = computed(() => {
|
||
const val = structureState.structureInfo.value
|
||
console.log('🔵 computedStructureInfo 计算:', val)
|
||
return val
|
||
})
|
||
|
||
// 添加调试监听,检查响应式
|
||
watch(() => structureState.structureInfo.value, (newVal, oldVal) => {
|
||
// ... 所有 watch 代码
|
||
}, { deep: true, immediate: true })
|
||
watch(() => structureState.structureData.value, (newVal, oldVal) => {
|
||
// ... 所有 watch 代码
|
||
}, { deep: true, immediate: true })
|
||
```
|
||
|
||
**替换为**(在第 164 行之后添加):
|
||
```typescript
|
||
// 新架构:使用单例 Store(事件驱动)
|
||
const structureStore = useStructureStore()
|
||
// 直接使用 Store 的状态(无需计算属性,无需 watch)
|
||
// 状态是只读的,通过 Store 方法修改
|
||
```
|
||
|
||
---
|
||
|
||
### 步骤 3:修改组件传参(第 65-68 行)
|
||
|
||
**原代码**:
|
||
```vue
|
||
<ResultPanel
|
||
:structure-loading="computedStructureLoading"
|
||
:structure-error="computedStructureError"
|
||
:structure-data="computedStructureData"
|
||
:structure-info="computedStructureInfo || undefined"
|
||
:edit-mode="structureEditMode"
|
||
@toggle-editor="toggleEditor"
|
||
@refresh-structure="refreshStructure"
|
||
@switch-to-edit-mode="handleSwitchToEditMode"
|
||
@switch-to-view-mode="handleSwitchToViewMode"
|
||
@save-structure="handleSaveStructure"
|
||
@cancel-edit="handleCancelEdit"
|
||
/>
|
||
```
|
||
|
||
**修改为**:
|
||
```vue
|
||
<ResultPanel
|
||
:structure-loading="structureStore.loading"
|
||
:structure-error="structureStore.error"
|
||
:structure-data="structureStore.data"
|
||
:structure-info="structureStore.info"
|
||
:edit-mode="structureEditMode"
|
||
@toggle-editor="toggleEditor"
|
||
@refresh-structure="structureStore.refreshStructure"
|
||
@switch-to-edit-mode="handleSwitchToEditMode"
|
||
@switch-to-view-mode="handleSwitchToViewMode"
|
||
@save-structure="handleSaveStructure"
|
||
@cancel-edit="handleCancelEdit"
|
||
/>
|
||
```
|
||
|
||
---
|
||
|
||
### 步骤 4:修改 `handleTableStructure` 函数(第 357-389 行)
|
||
|
||
**原代码**:
|
||
```typescript
|
||
const handleTableStructure = async (data: TableStructureEvent) => {
|
||
console.log('handleTableStructure 被调用:', data)
|
||
|
||
// ... Tab 切换代码 ...
|
||
|
||
// 加载表结构数据(在Tab切换后加载,确保用户能看到加载状态)
|
||
try {
|
||
await loadStructure(
|
||
data.connectionId,
|
||
data.database,
|
||
data.tableName,
|
||
data.dbType,
|
||
data.nodeType
|
||
)
|
||
// ... 大量调试日志 ...
|
||
} catch (error) {
|
||
console.error('handleTableStructure 出错:', error)
|
||
}
|
||
}
|
||
```
|
||
|
||
**修改为**:
|
||
```typescript
|
||
const handleTableStructure = async (data: TableStructureEvent) => {
|
||
console.log('🚀 handleTableStructure 被调用:', data)
|
||
|
||
// 如果结果面板隐藏,自动显示编辑器(这样结果面板也会显示)
|
||
if (!editorVisible.value) {
|
||
toggleEditor()
|
||
}
|
||
|
||
// 先切换到结果面板的"结构"Tab(确保Tab可见)
|
||
if (resultPanelRef.value) {
|
||
(resultPanelRef.value as any).switchToStructureTab()
|
||
}
|
||
|
||
// 等待一下确保Tab切换完成
|
||
await new Promise(resolve => setTimeout(resolve, 100))
|
||
|
||
// 新架构:直接调用 Store 的 loadStructure 方法
|
||
// Store 会自动管理状态和事件通知,无需手动追踪
|
||
await structureStore.loadStructure(
|
||
data.connectionId,
|
||
data.database,
|
||
data.tableName,
|
||
data.dbType,
|
||
data.nodeType
|
||
)
|
||
|
||
console.log('✅ 加载完成,Store 当前状态:', {
|
||
loading: structureStore.loading.value,
|
||
data: structureStore.data.value,
|
||
info: structureStore.info.value,
|
||
error: structureStore.error.value
|
||
})
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 步骤 5:修改 `handleRefreshStructure` 函数(第 456-462 行)
|
||
|
||
**原代码**:
|
||
```typescript
|
||
const handleRefreshStructure = async () => {
|
||
await refreshStructure()
|
||
}
|
||
```
|
||
|
||
**修改为**:
|
||
```typescript
|
||
const handleRefreshStructure = async () => {
|
||
await structureStore.refreshStructure()
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 步骤 6:删除未使用的导入
|
||
|
||
检查是否有其他 `useStructureState` 的使用,全部替换为 `useStructureStore`
|
||
|
||
---
|
||
|
||
## 验证迁移
|
||
|
||
完成以上步骤后,验证以下内容:
|
||
|
||
### 1. 检查日志输出
|
||
|
||
运行应用,点击表结构,应该看到以下日志:
|
||
|
||
```
|
||
🚀 handleTableStructure 被调用: { connectionId: 6, database: 'flux_pro', tableName: 'clue_info', dbType: 'mysql', nodeType: 'table' }
|
||
📢 事件触发 [structure:loading]: { loading: true }
|
||
🏪 Store.loadStructure 开始: { connectionId: 6, database: 'flux_pro', tableName: 'clue_info', dbType: 'mysql', nodeType: 'table' }
|
||
🏪 表结构加载成功: { connectionId: 6, database: 'flux_pro', tableName: 'clue_info', result: {...} }
|
||
🏪 Store.setData: { data: {...}, info: {...} }
|
||
📢 事件触发 [structure:data]: { data: {...}, info: {...} }
|
||
📢 事件触发 [structure:loading]: { loading: false }
|
||
✅ 加载完成,Store 当前状态: { loading: false, data: {...}, info: {...}, error: '' }
|
||
```
|
||
|
||
### 2. 检查界面
|
||
|
||
切换到"结构"标签页,应该能看到:
|
||
- ✅ 红色测试框(如果存在)
|
||
- ✅ 调试信息块显示正确的数据
|
||
- ✅ 表结构数据正常显示
|
||
|
||
### 3. 删除调试代码
|
||
|
||
确认功能正常后,删除:
|
||
- `ResultPanel.vue` 中的红色调试框
|
||
- `ResultPanel.vue` 中的全局调试信息
|
||
- `index.vue` 中不必要的日志
|
||
|
||
---
|
||
|
||
## 新架构的优势
|
||
|
||
### 1. 单一数据源
|
||
- 所有状态集中在 Store
|
||
- 避免多个 Composable 实例
|
||
- 全局共享,不会丢失
|
||
|
||
### 2. 事件驱动
|
||
- 所有状态变更自动通知
|
||
- 可追踪完整的事件流
|
||
- 易于调试和问题定位
|
||
|
||
### 3. 自动响应式
|
||
- Store 自动处理响应式
|
||
- 无需手动计算属性
|
||
- 无需 watch 监听
|
||
|
||
### 4. 类型安全
|
||
- 完整的 TypeScript 类型定义
|
||
- 事件和状态都有类型约束
|
||
- 编译时错误检查
|
||
|
||
### 5. 清晰的日志
|
||
- 所有关键操作都有日志
|
||
- 使用 emoji 标识不同的日志来源
|
||
- 易于过滤和搜索
|
||
|
||
---
|
||
|
||
## 故障排除
|
||
|
||
### 问题:Store 数据为 null
|
||
|
||
**可能原因**:
|
||
1. 组件未正确引用 Store
|
||
2. 事件未正确触发
|
||
3. Store 方法未正确调用
|
||
|
||
**解决方法**:
|
||
1. 检查控制台是否有 `🏪` 开头的日志
|
||
2. 检查是否有 `📢` 开头的日志
|
||
3. 确认 Store 是单例(只有一次 `useStructureStore` 调用)
|
||
|
||
### 问题:Tab 内容不显示
|
||
|
||
**可能原因**:
|
||
1. Arco Tabs 配置问题
|
||
2. CSS 样式冲突
|
||
3. 数据未正确传递
|
||
|
||
**解决方法**:
|
||
1. 检查 props 是否正确传递
|
||
2. 检查 CSS 中 `display: flex !important` 是否生效
|
||
3. 检查浏览器开发工具中的元素状态
|
||
|
||
---
|
||
|
||
## 后续改进
|
||
|
||
1. **引入 Pinia**(可选)
|
||
- 更强大的状态管理
|
||
- 内置 DevTools 支持
|
||
- 持久化支持
|
||
|
||
2. **添加单元测试**
|
||
- 测试 Store 的各种场景
|
||
- 测试事件总线的可靠性
|
||
- 提高代码质量
|
||
|
||
3. **性能优化**
|
||
- 使用 `shallowRef` 处理大数据
|
||
- 添加防抖和节流
|
||
- 优化事件监听
|
||
|
||
4. **错误边界**
|
||
- 全局错误捕获
|
||
- 错误恢复机制
|
||
- 用户友好的错误提示
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
新的事件驱动架构解决了当前的核心问题:
|
||
|
||
✅ **状态丢失问题** - 单例模式确保全局唯一实例
|
||
✅ **响应式失效问题** - 自动事件通知,无需手动追踪
|
||
✅ **调试困难问题** - 完整的日志体系,清晰的事件流
|
||
✅ **组件通信问题** - 事件总线解耦,易于维护
|
||
|
||
**下一步**:按照上述步骤手动完成代码迁移,然后测试验证。
|