217 lines
6.3 KiB
Markdown
217 lines
6.3 KiB
Markdown
# 数据库客户端组件拆分方案
|
||
|
||
## 组件架构设计
|
||
|
||
### 组件拆分
|
||
|
||
将 `index.vue` 拆分为以下组件:
|
||
|
||
1. **ConnectionTree.vue** - 左侧连接树形列表
|
||
2. **SqlEditor.vue** - SQL编辑器区域
|
||
3. **ResultPanel.vue** - 结果展示区域
|
||
4. **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`: 执行选中的SQL
|
||
- `format`: 格式化SQL
|
||
- `sql-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` → SqlEditor
|
||
- `loading, 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
|
||
|
||
### 优势
|
||
|
||
1. **职责清晰**:每个组件只关注自己的功能
|
||
2. **可维护性强**:修改某个功能只需修改对应组件
|
||
3. **可复用性**:ResultPanel 可以在其他地方复用
|
||
4. **测试友好**:每个组件可以独立测试
|
||
5. **性能优化**:可以针对单个组件进行优化
|
||
|
||
### 后续扩展
|
||
|
||
如果功能继续增加,可以考虑:
|
||
1. 引入 Pinia/Vuex 进行全局状态管理
|
||
2. 使用 provide/inject 传递深层数据
|
||
3. 提取公共逻辑到 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 中的通信代码
|
||
|
||
```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>
|
||
```
|
||
|