fdb2 1.0.11 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -122,8 +122,13 @@
122
122
  <i class="bi bi-table"></i>
123
123
  </div>
124
124
  <div class="table-info">
125
- <div class="table-name">{{ table.name }}</div>
126
- <div class="table-engine">{{ table.engine || '-' }}</div>
125
+ <div class="table-name-wrapper">
126
+ <div class="table-name" :title="table.name">{{ table.name }}</div>
127
+ <div class="table-engine">{{ table.engine || '-' }}</div>
128
+ </div>
129
+ <div class="table-comment-header" v-if="table.comment" :title="table.comment">
130
+ {{ table.comment }}
131
+ </div>
127
132
  </div>
128
133
  </div>
129
134
  <div class="card-body">
@@ -137,9 +142,6 @@
137
142
  <span class="stat-value">{{ formatSize(table.dataSize) }}</span>
138
143
  </div>
139
144
  </div>
140
- <div class="table-comment" v-if="table.comment">
141
- {{ table.comment }}
142
- </div>
143
145
  <div class="table-actions">
144
146
  <button class="btn btn-sm btn-outline-primary" @click.stop="editTable(table)">
145
147
  <i class="bi bi-pencil"></i>
@@ -998,10 +1000,24 @@ function handleExecuteSQL(sql: string) {
998
1000
  color: white;
999
1001
  }
1000
1002
 
1003
+ .table-info {
1004
+ flex: 1;
1005
+ min-width: 0; /* 允许子元素截断 */
1006
+ }
1007
+
1008
+ .table-name-wrapper {
1009
+ display: flex;
1010
+ align-items: center;
1011
+ gap: 0.5rem;
1012
+ margin-bottom: 0.25rem;
1013
+ }
1014
+
1001
1015
  .table-name {
1002
1016
  font-weight: 600;
1003
1017
  color: #1e293b;
1004
- margin-bottom: 0.25rem;
1018
+ white-space: nowrap;
1019
+ overflow: hidden;
1020
+ text-overflow: ellipsis;
1005
1021
  }
1006
1022
 
1007
1023
  .table-engine {
@@ -1010,6 +1026,15 @@ function handleExecuteSQL(sql: string) {
1010
1026
  background: #f1f5f9;
1011
1027
  padding: 0.125rem 0.375rem;
1012
1028
  border-radius: 8px;
1029
+ flex-shrink: 0;
1030
+ }
1031
+
1032
+ .table-comment-header {
1033
+ font-size: 0.75rem;
1034
+ color: #64748b;
1035
+ white-space: nowrap;
1036
+ overflow: hidden;
1037
+ text-overflow: ellipsis;
1013
1038
  }
1014
1039
 
1015
1040
  .card-body {
@@ -0,0 +1,273 @@
1
+ <template>
2
+ <div class="table-data-grid">
3
+ <div class="data-grid-wrapper">
4
+ <DataGrid
5
+ :data="tableData"
6
+ :columns="gridColumns"
7
+ :is-loading="loading"
8
+ :total-pages="totalPages"
9
+ :current-page="currentPage"
10
+ :sort-field="sortField"
11
+ :sort-order="sortOrder"
12
+ @page-changed="handlePageChange"
13
+ @sort-changed="handleSortChange"
14
+ >
15
+ <!-- 为操作列定义 slot -->
16
+ <template #actions="{ row }">
17
+ <div class="btn-group btn-group-sm">
18
+ <button class="btn btn-outline-primary btn-sm" @click.stop="emits('edit-row', row)">
19
+ <i class="bi bi-pencil"></i>
20
+ </button>
21
+ <button class="btn btn-outline-danger btn-sm" @click.stop="emits('delete-row', row)">
22
+ <i class="bi bi-trash"></i>
23
+ </button>
24
+ </div>
25
+ </template>
26
+
27
+ <!-- 为每一列定义 header slot,以显示类型和主键图标 -->
28
+ <template v-for="column in props.columns" :key="column.name + '_header'" #[column.name+`_header`]>
29
+ <div class="column-header-custom" @click="handleSortChange({field: column.name, order: sortField === column.name ? (sortOrder === 'ASC' ? 'DESC' : (sortOrder === 'DESC' ? '' : 'ASC')) : 'ASC'})" :title="column.comment || column.name">
30
+ <div class="header-main">
31
+ <span class="column-name">{{ column.name }}</span>
32
+ <span v-if="column.isPrimary" class="column-key text-primary ms-1" title="主键">
33
+ <i class="bi bi-key-fill"></i>
34
+ </span>
35
+ <span class="sort-icon ms-1">
36
+ <i v-if="sortField === column.name && sortOrder === 'ASC'" class="bi bi-caret-up-fill"></i>
37
+ <i v-else-if="sortField === column.name && sortOrder === 'DESC'" class="bi bi-caret-down-fill"></i>
38
+ <i v-else class="bi bi-caret-up text-muted opacity-50"></i>
39
+ </span>
40
+ </div>
41
+ <div class="header-sub">
42
+ <small class="text-muted column-type">{{ column.type }}</small>
43
+ <small v-if="column.comment" class="text-muted column-comment ms-1">- {{ column.comment }}</small>
44
+ </div>
45
+ </div>
46
+ </template>
47
+
48
+ <!-- 自定义单元格渲染 -->
49
+ <template v-for="column in props.columns" :key="column.name" #[column.name]="{ row }">
50
+ <div class="cell-value" :title="String(row[column.name])">
51
+ {{ formatCellValue(row[column.name]) }}
52
+ </div>
53
+ </template>
54
+
55
+ <!-- 空状态插槽 -->
56
+ <template #footer v-if="!loading && tableData.length === 0">
57
+ <tr>
58
+ <td :colspan="gridColumns.length" class="text-center py-5">
59
+ <div class="empty-state">
60
+ <i class="bi bi-inbox fs-1 text-muted"></i>
61
+ <p class="mt-2 text-muted">表中暂无数据</p>
62
+ </div>
63
+ </td>
64
+ </tr>
65
+ </template>
66
+ </DataGrid>
67
+ </div>
68
+
69
+ <!-- 底部状态栏 -->
70
+ <div class="grid-footer-bar mt-2 d-flex justify-content-between align-items-center px-3">
71
+ <div class="pagination-info text-muted small">
72
+ 共 {{ total }} 条记录
73
+ </div>
74
+ <div class="page-size-selector d-flex align-items-center">
75
+ <label class="small text-muted me-2">每页显示:</label>
76
+ <select class="form-select form-select-sm" v-model="pageSize" @change="handlePageSizeChange" style="width: 80px;">
77
+ <option :value="10">10</option>
78
+ <option :value="20">20</option>
79
+ <option :value="50">50</option>
80
+ <option :value="100">100</option>
81
+ </select>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </template>
86
+
87
+ <script setup lang="ts">
88
+ import { ref, computed, watch, onMounted } from 'vue';
89
+ import DataGrid from '@/components/dataGrid/index.vue';
90
+ import type { ConnectionEntity, TableEntity, ColumnEntity } from '@/typings/database';
91
+ import { DatabaseService } from '@/service/database';
92
+
93
+ const props = defineProps<{
94
+ connection: ConnectionEntity | null;
95
+ database: string;
96
+ table: TableEntity | null;
97
+ columns: ColumnEntity[];
98
+ }>();
99
+
100
+ const emits = defineEmits<{
101
+ 'edit-row': [row: any];
102
+ 'delete-row': [row: any];
103
+ }>();
104
+
105
+ const databaseService = new DatabaseService();
106
+
107
+ // 状态
108
+ const tableData = ref<any[]>([]);
109
+ const loading = ref(false);
110
+ const total = ref(0);
111
+ const currentPage = ref(1);
112
+ const pageSize = ref(50);
113
+ const sortField = ref('');
114
+ const sortOrder = ref(''); // ASC, DESC, ''
115
+
116
+ // 计算属性
117
+ const totalPages = computed(() => Math.ceil(total.value / pageSize.value) || 1);
118
+
119
+ const gridColumns = computed(() => {
120
+ const cols = props.columns.map(col => ({
121
+ name: col.name,
122
+ text: col.name,
123
+ sortable: true
124
+ }));
125
+
126
+ // 添加操作列
127
+ cols.push({
128
+ name: 'actions',
129
+ text: '操作',
130
+ sortable: false,
131
+ headerStyle: 'width: 100px;'
132
+ } as any);
133
+
134
+ return cols;
135
+ });
136
+
137
+ // 方法
138
+ async function loadData() {
139
+ if (!props.connection || !props.database || !props.table) return;
140
+
141
+ loading.value = true;
142
+ try {
143
+ let orderBy = '';
144
+ if (sortField.value && sortOrder.value) {
145
+ orderBy = `${sortField.value} ${sortOrder.value}`;
146
+ }
147
+
148
+ const result = await databaseService.getTableData(
149
+ props.connection.id,
150
+ props.database,
151
+ props.table.name,
152
+ currentPage.value,
153
+ pageSize.value,
154
+ undefined,
155
+ orderBy
156
+ );
157
+
158
+ if (result.ret === 0) {
159
+ tableData.value = result.data?.data || [];
160
+ total.value = result.data?.total || 0;
161
+ }
162
+ } catch (error) {
163
+ console.error('加载表数据失败:', error);
164
+ } finally {
165
+ loading.value = false;
166
+ }
167
+ }
168
+
169
+ function handlePageChange(page: number) {
170
+ currentPage.value = page;
171
+ loadData();
172
+ }
173
+
174
+ function handlePageSizeChange() {
175
+ currentPage.value = 1;
176
+ loadData();
177
+ }
178
+
179
+ function handleSortChange({ field, order }: { field: string, order: string }) {
180
+ sortField.value = field;
181
+ sortOrder.value = order;
182
+ currentPage.value = 1;
183
+ loadData();
184
+ }
185
+
186
+ function formatCellValue(value: any): string {
187
+ if (value === null || value === undefined) return 'NULL';
188
+ if (typeof value === 'object') return JSON.stringify(value);
189
+ return String(value);
190
+ }
191
+
192
+ // 暴露刷新方法
193
+ defineExpose({
194
+ refresh: loadData
195
+ });
196
+
197
+ // 监听变化
198
+ watch(() => [props.connection?.id, props.database, props.table?.name], () => {
199
+ currentPage.value = 1;
200
+ sortField.value = '';
201
+ sortOrder.value = '';
202
+ loadData();
203
+ });
204
+
205
+ onMounted(() => {
206
+ loadData();
207
+ });
208
+ </script>
209
+
210
+ <style scoped>
211
+ .table-data-grid {
212
+ display: flex;
213
+ flex-direction: column;
214
+ height: 100%;
215
+ }
216
+
217
+ .data-grid-wrapper {
218
+ flex: 1;
219
+ overflow: hidden;
220
+ }
221
+
222
+ .column-header-custom {
223
+ display: flex;
224
+ flex-direction: column;
225
+ cursor: pointer;
226
+ user-select: none;
227
+ }
228
+
229
+ .header-main {
230
+ display: flex;
231
+ align-items: center;
232
+ }
233
+
234
+ .column-name {
235
+ font-weight: 600;
236
+ }
237
+
238
+ .header-sub {
239
+ display: flex;
240
+ align-items: center;
241
+ margin-top: 2px;
242
+ max-width: 150px; /* 限制子标题区域的最大宽度 */
243
+ }
244
+
245
+ .column-type {
246
+ font-size: 0.7rem;
247
+ line-height: 1;
248
+ flex-shrink: 0; /* 防止类型被压缩 */
249
+ }
250
+
251
+ .column-comment {
252
+ font-size: 0.7rem;
253
+ line-height: 1;
254
+ opacity: 0.8;
255
+ white-space: nowrap;
256
+ overflow: hidden;
257
+ text-overflow: ellipsis;
258
+ flex: 1; /* 允许注释占据剩余空间并截断 */
259
+ }
260
+
261
+ .cell-value {
262
+ max-width: 300px;
263
+ overflow: hidden;
264
+ text-overflow: ellipsis;
265
+ white-space: nowrap;
266
+ }
267
+
268
+ .grid-footer-bar {
269
+ background: #f8f9fa;
270
+ border-top: 1px solid #dee2e6;
271
+ padding: 8px 16px;
272
+ }
273
+ </style>