6.3 KiB
6.3 KiB
数据库客户端组件拆分方案
组件架构设计
组件拆分
将 index.vue 拆分为以下组件:
- ConnectionTree.vue - 左侧连接树形列表
- SqlEditor.vue - SQL编辑器区域
- ResultPanel.vue - 结果展示区域
- index.vue - 主组件(布局和状态管理)
组件职责划分
ConnectionTree.vue
- 职责:连接列表管理、树形结构展示、数据库/表展开
- 状态:connections, treeData, loading, loadingNodes
- 方法:loadConnections, loadDatabases, loadTables
- 事件:
connection-select: 连接被选中connection-edit: 编辑连接connection-delete: 删除连接connection-refresh: 需要刷新连接列表table-select: 表被选中(用于生成SQL)new-connection: 新建连接
SqlEditor.vue
- 职责:SQL编辑器、标签页管理、工具栏
- Props:
currentConnection: 当前选中的连接对象
- 状态:tabs, activeTab, editorView
- 方法:initEditor, handleAddTab, handleDeleteTab, handleExecute, handleExecuteSelected, handleFormat
- 事件:
execute: 执行SQL(完整内容)execute-selected: 执行选中的SQLformat: 格式化SQLsql-insert: 插入SQL到编辑器(由表选择触发)tab-change: 标签页切换sql-change: SQL内容变化
ResultPanel.vue
- 职责:结果展示(表格、JSON、消息)
- Props:
loading: 加载状态error: 错误信息data: 结果数据mode: 展示模式(table/json)stats: 执行统计信息messages: 消息列表
- 状态:resultTab
- 方法:formatJSON
- 事件:无(纯展示组件)
index.vue(主组件)
- 职责:
- 布局管理(左侧、右侧、底部)
- 状态协调(当前连接、执行结果)
- 组件通信桥梁
- 连接表单管理
组件通信方式
1. Props 向下传递
currentConnection→ SqlEditorloading, error, data, mode, stats, messages→ ResultPanel
2. Events 向上传递
- ConnectionTree 的事件 → index.vue 处理
- SqlEditor 的事件 → index.vue 处理
3. 数据流向
ConnectionTree
└─ connection-select ──→ index.vue ──→ SqlEditor (currentConnection prop)
└─→ ResultPanel (clear data)
SqlEditor
└─ execute ──→ index.vue ──→ ExecuteSQL API ──→ ResultPanel (result props)
ConnectionTree
└─ table-select ──→ index.vue ──→ SqlEditor (sql-insert event)
状态管理
主组件 (index.vue) 管理的状态:
currentConnection: 当前选中的连接(需要传递给 SqlEditor)resultLoading, resultError, resultData, resultMode, resultStats: 执行结果(需要传递给 ResultPanel)messages: 消息列表(需要传递给 ResultPanel)showConnectionForm, editingConnectionId: 连接表单状态
子组件自己管理的状态:
- ConnectionTree: connections, treeData, loading, loadingNodes
- SqlEditor: tabs, activeTab, editorView
- ResultPanel: resultTab
优势
- 职责清晰:每个组件只关注自己的功能
- 可维护性强:修改某个功能只需修改对应组件
- 可复用性:ResultPanel 可以在其他地方复用
- 测试友好:每个组件可以独立测试
- 性能优化:可以针对单个组件进行优化
后续扩展
如果功能继续增加,可以考虑:
- 引入 Pinia/Vuex 进行全局状态管理
- 使用 provide/inject 传递深层数据
- 提取公共逻辑到 composables
实现步骤
步骤1:创建 ConnectionTree.vue ✅
已完成,组件位置:components/ConnectionTree.vue
步骤2:创建 SqlEditor.vue
需要提取的代码:
- 编辑器相关:initEditor, editorView, tabs, activeTab
- 标签页管理:handleAddTab, handleDeleteTab
- 执行方法:handleExecute, handleExecuteSelected(通过emit传递SQL给父组件)
- 格式化:handleFormat
- SQL插入:insertSQL(用于接收表选择事件)
步骤3:创建 ResultPanel.vue
需要提取的代码:
- 结果展示:resultLoading, resultError, resultData, resultMode, resultStats, resultColumns
- 消息列表:messages
- 格式化:formatJSON
步骤4:重构 index.vue
- 移除已提取的代码
- 引入新组件
- 实现组件通信逻辑:
- 监听 ConnectionTree 的事件
- 调用 ExecuteSQL API
- 传递数据到 ResultPanel
通信示例代码
index.vue 中的通信代码
<template>
<a-layout class="db-cli-layout">
<a-layout-sider :width="280">
<ConnectionTree
:current-connection-id="currentConnection?.id"
@connection-select="handleConnectionSelect"
@connection-edit="handleConnectionEdit"
@connection-delete="handleConnectionDelete"
@table-select="handleTableSelect"
@new-connection="showConnectionForm = true"
/>
</a-layout-sider>
<a-layout class="right-layout">
<a-layout-content>
<SqlEditor
:current-connection="currentConnection"
@execute="handleExecuteSQL"
@execute-selected="handleExecuteSelectedSQL"
@sql-insert="handleSQLInsert"
/>
</a-layout-content>
<a-layout-footer>
<ResultPanel
:loading="resultLoading"
:error="resultError"
:data="resultData"
:mode="resultMode"
:stats="resultStats"
:messages="messages"
/>
</a-layout-footer>
</a-layout>
</a-layout>
</template>
<script setup>
// 主组件只负责状态管理和组件协调
const currentConnection = ref(null)
const resultLoading = ref(false)
// ... 其他状态
// 连接选择
const handleConnectionSelect = (conn) => {
currentConnection.value = conn
// 清空结果
clearResults()
}
// SQL执行
const handleExecuteSQL = async (sql) => {
resultLoading.value = true
try {
const result = await window.go.main.App.ExecuteSQL(currentConnection.value.id, sql)
// 处理结果,更新 resultData, resultStats 等
} catch (error) {
// 处理错误
} finally {
resultLoading.value = false
}
}
// SQL插入
const handleSQLInsert = (sql) => {
// 通过 ref 调用 SqlEditor 的方法
sqlEditorRef.value?.insertSQL(sql)
}
</script>