9.2 KiB
9.2 KiB
架构迁移完成指南 - 事件驱动架构
当前状态
已创建以下新文件:
-
frontend/src/views/db-cli/composables/useEventBus.ts- 事件总线- 类型安全的事件定义
- 支持事件订阅/取消/触发
- 自动错误处理和日志
-
frontend/src/views/db-cli/composables/useStructureStore.ts- 新的表结构 Store- 单例模式,全局共享状态
- 事件驱动的状态更新
- 清晰的日志追踪
-
frontend/src/views/db-cli/composables/useStructureStoreLegacy.ts- 旧版本(已重命名)- 原
useStructureState.ts的副本 - 保留用于兼容和参考
- 原
-
frontend/src/views/db-cli/composables/MIGRATION.md- 迁移文档- 详细的对表和迁移步骤
- 使用示例和注意事项
手动完成迁移步骤
步骤 1:修改 index.vue 的导入
位置:frontend/src/views/db-cli/index.vue 第 120 行
原代码:
import { useStructureState } from './composables/useStructureState'
修改为:
import { useStructureStore } from './composables/useStructureStore'
步骤 2:替换状态初始化(第 166-219 行)
原代码(删除第 166-219 行):
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 行之后添加):
// 新架构:使用单例 Store(事件驱动)
const structureStore = useStructureStore()
// 直接使用 Store 的状态(无需计算属性,无需 watch)
// 状态是只读的,通过 Store 方法修改
步骤 3:修改组件传参(第 65-68 行)
原代码:
<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"
/>
修改为:
<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 行)
原代码:
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)
}
}
修改为:
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 行)
原代码:
const handleRefreshStructure = async () => {
await refreshStructure()
}
修改为:
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
可能原因:
- 组件未正确引用 Store
- 事件未正确触发
- Store 方法未正确调用
解决方法:
- 检查控制台是否有
🏪开头的日志 - 检查是否有
📢开头的日志 - 确认 Store 是单例(只有一次
useStructureStore调用)
问题:Tab 内容不显示
可能原因:
- Arco Tabs 配置问题
- CSS 样式冲突
- 数据未正确传递
解决方法:
- 检查 props 是否正确传递
- 检查 CSS 中
display: flex !important是否生效 - 检查浏览器开发工具中的元素状态
后续改进
-
引入 Pinia(可选)
- 更强大的状态管理
- 内置 DevTools 支持
- 持久化支持
-
添加单元测试
- 测试 Store 的各种场景
- 测试事件总线的可靠性
- 提高代码质量
-
性能优化
- 使用
shallowRef处理大数据 - 添加防抖和节流
- 优化事件监听
- 使用
-
错误边界
- 全局错误捕获
- 错误恢复机制
- 用户友好的错误提示
总结
新的事件驱动架构解决了当前的核心问题:
✅ 状态丢失问题 - 单例模式确保全局唯一实例 ✅ 响应式失效问题 - 自动事件通知,无需手动追踪 ✅ 调试困难问题 - 完整的日志体系,清晰的事件流 ✅ 组件通信问题 - 事件总线解耦,易于维护
下一步:按照上述步骤手动完成代码迁移,然后测试验证。