fix: 移除数据管理重复横向滚动条

This commit is contained in:
2026-05-19 11:34:27 +08:00
parent f82c36c485
commit a4b8e2c6e5
2 changed files with 8 additions and 112 deletions
+6
View File
@@ -1,5 +1,11 @@
# 项目上下文记录 # 项目上下文记录
## 2026-05-19:移除数据管理表格重复横向滚动条
- `frontend/src/components/DatabasePanel.vue` 移除了上一版额外添加的 `.database-horizontal-scrollbar` 外置滚动条和同步滚动逻辑,避免与 Naive UI DataTable 自带横向滚动条同时显示。
- 数据表仍保留 `scroll-x` 和列最小宽度计算,由 Naive UI 原生表格滚动条负责横向浏览,字段结构折叠头继续保留“字段结构 / 收起字段结构”状态文案。
- 已执行 `npm run build`;已通过本机 Chrome DevTools 验证 `/database` 中外置滚动条 DOM 数量为 0,表格自身仍存在横向溢出滚动。
## 2026-05-19:修复数据管理表格滚动和字段结构收起 ## 2026-05-19:修复数据管理表格滚动和字段结构收起
- `frontend/src/components/DatabasePanel.vue` 为数据表增加明确的 `scroll-x` 宽度和底部外置横向滚动条,滚动条会与 Naive UI 表格内部横向滚动位置双向同步,避免字段结构区域或分页区域遮挡表格底部横向滚动入口。 - `frontend/src/components/DatabasePanel.vue` 为数据表增加明确的 `scroll-x` 宽度和底部外置横向滚动条,滚动条会与 Naive UI 表格内部横向滚动位置双向同步,避免字段结构区域或分页区域遮挡表格底部横向滚动入口。
+2 -112
View File
@@ -98,7 +98,7 @@
<div class="database-data-body" :class="{ empty: !selectedTable }"> <div class="database-data-body" :class="{ empty: !selectedTable }">
<n-empty v-if="!selectedTable" description="请选择左侧的数据表" /> <n-empty v-if="!selectedTable" description="请选择左侧的数据表" />
<template v-else> <template v-else>
<div ref="dataTableWrapperRef" class="database-data-table-wrap"> <div class="database-data-table-wrap">
<n-data-table <n-data-table
size="small" size="small"
class="database-data-table" class="database-data-table"
@@ -111,15 +111,6 @@
flex-height flex-height
/> />
</div> </div>
<div
v-if="hasDataColumns"
ref="horizontalScrollRef"
class="database-horizontal-scrollbar"
aria-label="数据表横向滚动条"
@scroll="syncTableScrollFromBar"
>
<div class="database-horizontal-scrollbar-inner" :style="{ width: `${dataTableScrollX}px` }"></div>
</div>
<section v-if="tableInfo" class="columns-collapse"> <section v-if="tableInfo" class="columns-collapse">
<button type="button" class="columns-collapse-header" @click="toggleColumnSchema"> <button type="button" class="columns-collapse-header" @click="toggleColumnSchema">
@@ -173,7 +164,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue'; import { computed, onActivated, onBeforeUnmount, onMounted, ref } from 'vue';
import { onBeforeRouteLeave } from 'vue-router'; import { onBeforeRouteLeave } from 'vue-router';
import { useDialog, useMessage } from 'naive-ui'; import { useDialog, useMessage } from 'naive-ui';
import type { DataTableColumns, DropdownOption } from 'naive-ui'; import type { DataTableColumns, DropdownOption } from 'naive-ui';
@@ -207,12 +198,8 @@ const loadingTables = ref(false);
const loadingData = ref(false); const loadingData = ref(false);
const testing = ref(false); const testing = ref(false);
const downloadingFormat = ref<DownloadFormat | null>(null); const downloadingFormat = ref<DownloadFormat | null>(null);
const dataTableWrapperRef = ref<HTMLElement | null>(null);
const horizontalScrollRef = ref<HTMLElement | null>(null);
const columnSchemaExpanded = ref(false); const columnSchemaExpanded = ref(false);
let tableLoadToken = 0; let tableLoadToken = 0;
let removeTableScrollListener: (() => void) | null = null;
let syncingHorizontalScroll = false;
const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value))); const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)));
const downloading = computed(() => Boolean(downloadingFormat.value)); const downloading = computed(() => Boolean(downloadingFormat.value));
@@ -236,7 +223,6 @@ const columnKeys = computed(() => (
? Object.keys(rows.value[0]) ? Object.keys(rows.value[0])
: tableInfo.value?.columns.map(column => String(column.Field || '')).filter(Boolean) || [] : tableInfo.value?.columns.map(column => String(column.Field || '')).filter(Boolean) || []
)); ));
const hasDataColumns = computed(() => columnKeys.value.length > 0);
const dataTableScrollX = computed(() => Math.max(columnKeys.value.length * COLUMN_MIN_WIDTH, COLUMN_MIN_WIDTH)); const dataTableScrollX = computed(() => Math.max(columnKeys.value.length * COLUMN_MIN_WIDTH, COLUMN_MIN_WIDTH));
const columns = computed<DataTableColumns<RowData>>(() => { const columns = computed<DataTableColumns<RowData>>(() => {
const keys = columnKeys.value; const keys = columnKeys.value;
@@ -250,10 +236,6 @@ const columns = computed<DataTableColumns<RowData>>(() => {
})); }));
}); });
watch([dataTableScrollX, rows, tableInfo], () => {
void refreshHorizontalScrollBinding();
}, { flush: 'post' });
onMounted(() => { onMounted(() => {
setPageHeader({ setPageHeader({
actions: [ actions: [
@@ -281,7 +263,6 @@ onBeforeRouteLeave(() => {
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
removeHorizontalScrollListener();
resetPageHeader(); resetPageHeader();
}); });
@@ -315,7 +296,6 @@ async function loadTables() {
rows.value = []; rows.value = [];
total.value = 0; total.value = 0;
columnSchemaExpanded.value = false; columnSchemaExpanded.value = false;
removeHorizontalScrollListener();
} }
} catch (error) { } catch (error) {
if (currentToken !== tableLoadToken) return; if (currentToken !== tableLoadToken) return;
@@ -338,7 +318,6 @@ function resetTableState() {
columnSchemaExpanded.value = false; columnSchemaExpanded.value = false;
loadingTables.value = false; loadingTables.value = false;
loadingData.value = false; loadingData.value = false;
removeHorizontalScrollListener();
} }
async function loadTable(table: string) { async function loadTable(table: string) {
@@ -376,66 +355,11 @@ async function loadTableData() {
message.error(error instanceof Error ? error.message : '加载表数据失败'); message.error(error instanceof Error ? error.message : '加载表数据失败');
} finally { } finally {
loadingData.value = false; loadingData.value = false;
void refreshHorizontalScrollBinding();
} }
} }
function toggleColumnSchema() { function toggleColumnSchema() {
columnSchemaExpanded.value = !columnSchemaExpanded.value; columnSchemaExpanded.value = !columnSchemaExpanded.value;
void refreshHorizontalScrollBinding();
}
function findTableScrollContainer(): HTMLElement | null {
const wrapper = dataTableWrapperRef.value;
if (!wrapper) return null;
const candidates = Array.from(
wrapper.querySelectorAll<HTMLElement>('.n-scrollbar-container, .n-data-table-base-table-body, .n-data-table-base-table-header')
);
return candidates.find(element => element.scrollWidth > element.clientWidth + 1) || null;
}
function removeHorizontalScrollListener() {
removeTableScrollListener?.();
removeTableScrollListener = null;
}
async function refreshHorizontalScrollBinding() {
await nextTick();
removeHorizontalScrollListener();
const tableScroller = findTableScrollContainer();
const horizontalScroller = horizontalScrollRef.value;
if (!tableScroller || !horizontalScroller) return;
horizontalScroller.scrollLeft = tableScroller.scrollLeft;
const handleTableScroll = () => {
if (syncingHorizontalScroll) return;
syncingHorizontalScroll = true;
horizontalScroller.scrollLeft = tableScroller.scrollLeft;
requestAnimationFrame(() => {
syncingHorizontalScroll = false;
});
};
tableScroller.addEventListener('scroll', handleTableScroll, { passive: true });
removeTableScrollListener = () => {
tableScroller.removeEventListener('scroll', handleTableScroll);
};
}
function syncTableScrollFromBar() {
if (syncingHorizontalScroll) return;
const tableScroller = findTableScrollContainer();
const horizontalScroller = horizontalScrollRef.value;
if (!tableScroller || !horizontalScroller) return;
syncingHorizontalScroll = true;
tableScroller.scrollLeft = horizontalScroller.scrollLeft;
requestAnimationFrame(() => {
syncingHorizontalScroll = false;
});
} }
function changePage(value: number) { function changePage(value: number) {
@@ -506,7 +430,6 @@ async function dropTable() {
rows.value = []; rows.value = [];
total.value = 0; total.value = 0;
columnSchemaExpanded.value = false; columnSchemaExpanded.value = false;
removeHorizontalScrollListener();
await loadTables(); await loadTables();
} catch (error) { } catch (error) {
message.error(error instanceof Error ? error.message : '删除失败'); message.error(error instanceof Error ? error.message : '删除失败');
@@ -533,7 +456,6 @@ async function dropAllTables() {
rows.value = []; rows.value = [];
total.value = 0; total.value = 0;
columnSchemaExpanded.value = false; columnSchemaExpanded.value = false;
removeHorizontalScrollListener();
await loadTables(); await loadTables();
} catch (error) { } catch (error) {
message.error(error instanceof Error ? error.message : '删除全部数据表失败'); message.error(error instanceof Error ? error.message : '删除全部数据表失败');
@@ -823,38 +745,6 @@ function formatCell(value: unknown): string {
min-height: 0; min-height: 0;
} }
.database-horizontal-scrollbar {
height: 14px;
flex: 0 0 14px;
overflow-x: auto;
overflow-y: hidden;
background: var(--td-bg-color-container);
border-top: 1px solid var(--td-border-color-light);
scrollbar-color: var(--td-scrollbar-color) transparent;
scrollbar-width: thin;
}
.database-horizontal-scrollbar::-webkit-scrollbar {
height: 10px;
}
.database-horizontal-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.database-horizontal-scrollbar::-webkit-scrollbar-thumb {
background: var(--td-scrollbar-color);
border-radius: 8px;
}
.database-horizontal-scrollbar::-webkit-scrollbar-thumb:hover {
background: var(--td-text-color-placeholder);
}
.database-horizontal-scrollbar-inner {
height: 1px;
}
.columns-collapse { .columns-collapse {
flex: 0 0 auto; flex: 0 0 auto;
max-height: 40%; max-height: 40%;