From b809e8ee0a6fa2053226a66e62d4a1c3849532bd Mon Sep 17 00:00:00 2001 From: Nixevol Date: Tue, 19 May 2026 15:11:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=AF=BC=E5=87=BA=E4=B8=8B=E6=8B=89=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/project_context.md | 7 +++ frontend/src/AppShell.vue | 38 +++++++++++++- frontend/src/components/DatabasePanel.vue | 64 +++++++++++++++-------- frontend/src/composables/pageHeader.ts | 3 ++ frontend/src/styles.css | 10 ++++ 5 files changed, 100 insertions(+), 22 deletions(-) diff --git a/docs/project_context.md b/docs/project_context.md index 4514ed2..52c706c 100644 --- a/docs/project_context.md +++ b/docs/project_context.md @@ -1,5 +1,12 @@ # 项目上下文记录 +## 2026-05-19:优化数据管理导出入口和弹窗宽度 + +- `frontend/src/composables/pageHeader.ts` 和 `frontend/src/AppShell.vue` 为页面顶部动作支持 Naive UI 下拉菜单,按钮内显示下拉箭头。 +- `frontend/src/components/DatabasePanel.vue` 将顶部“导出 CSV / 导出 XLSX”两个按钮合并为一个“导出”下拉按钮,点击后选择 CSV 或 XLSX 再进入对应表选择弹窗。 +- CSV 和 XLSX 导出弹窗改为固定 420px 内的响应式宽度,避免在宽屏下铺满整页;表选择列表增加边框和背景,视觉上更集中。 +- 已执行 `npm run build`,构建通过;构建只保留 Vite 原有大 chunk 提示。 + ## 2026-05-19:调整数据管理导出交互并支持多表 XLSX - `frontend/src/components/DatabasePanel.vue` 将导出入口移到页面顶部,并放在“删除全部表”按钮左侧;内容区工具栏只保留刷新、清空、删除当前表。 diff --git a/frontend/src/AppShell.vue b/frontend/src/AppShell.vue index a40b57a..1008e46 100644 --- a/frontend/src/AppShell.vue +++ b/frontend/src/AppShell.vue @@ -42,6 +42,31 @@ {{ actionLabel(action) }} + + + + + {{ actionLabel(action) }} + + + + import { computed, h, onBeforeUnmount, onMounted, ref, watch, type Component } from 'vue'; import { RouterView, useRoute, useRouter } from 'vue-router'; -import { useDialog, useMessage, type MenuOption, NIcon } from 'naive-ui'; +import { useDialog, useMessage, type DropdownOption, type MenuOption, NIcon } from 'naive-ui'; import { + ChevronDownOutline, CloudUploadOutline, ConstructOutline, FileTrayFullOutline, @@ -294,10 +320,20 @@ function actionDisabled(action: PageHeaderAction) { return resolveHeaderValue(action.disabled, false); } +function actionDropdownOptions(action: PageHeaderAction) { + return resolveHeaderValue(action.dropdownOptions, []); +} + function runHeaderAction(action: PageHeaderAction) { void action.onClick?.(); } +function runHeaderActionSelect(action: PageHeaderAction) { + return (key: string | number, option: DropdownOption) => { + void action.onSelect?.(key, option); + }; +} + function preventWindowFileDrop(event: DragEvent) { const types = Array.from(event.dataTransfer?.types || []); if (!types.includes('Files')) { diff --git a/frontend/src/components/DatabasePanel.vue b/frontend/src/components/DatabasePanel.vue index e2c911e..d7393ec 100644 --- a/frontend/src/components/DatabasePanel.vue +++ b/frontend/src/components/DatabasePanel.vue @@ -146,7 +146,13 @@ - +

选择要导出的数据表。

@@ -175,7 +181,13 @@ - +

选择一个或多个数据表,导出后会合并到同一个 XLSX 文件中。

@@ -209,7 +221,7 @@ import { computed, onActivated, onBeforeUnmount, onMounted, ref } from 'vue'; import { onBeforeRouteLeave } from 'vue-router'; import { useDialog, useMessage } from 'naive-ui'; -import type { DataTableColumns } from 'naive-ui'; +import type { DataTableColumns, DropdownOption } from 'naive-ui'; import { ChevronForwardOutline, CloudDownloadOutline, @@ -248,9 +260,14 @@ let tableLoadToken = 0; const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value))); const downloading = computed(() => Boolean(downloadingFormat.value)); -const csvExportButtonText = computed(() => (downloadingFormat.value === 'csv' ? '导出 CSV...' : '导出 CSV')); -const xlsxExportButtonText = computed(() => (downloadingFormat.value === 'xlsx' ? '导出 XLSX...' : '导出 XLSX')); +const exportButtonText = computed(() => ( + downloadingFormat.value ? `导出 ${downloadingFormat.value.toUpperCase()}...` : '导出' +)); const exportDisabled = computed(() => loadingTables.value || tables.value.length === 0 || downloading.value); +const exportOptions: DropdownOption[] = [ + { label: 'CSV', key: 'csv' }, + { label: 'XLSX', key: 'xlsx' } +]; const loadDataInfileText = computed(() => { if (testing.value || !databaseInfo.value) return '检测中'; return databaseInfo.value.load_data_infile ? '可用' : '未启用'; @@ -281,24 +298,15 @@ onMounted(() => { setPageHeader({ actions: [ { - key: 'export-csv', - label: csvExportButtonText, + key: 'export', + label: exportButtonText, icon: CloudDownloadOutline, type: 'primary', variant: 'solid', - loading: computed(() => downloadingFormat.value === 'csv'), + loading: downloading, disabled: exportDisabled, - onClick: showCsvExportDialog - }, - { - key: 'export-xlsx', - label: xlsxExportButtonText, - icon: CloudDownloadOutline, - type: 'primary', - variant: 'outline', - loading: computed(() => downloadingFormat.value === 'xlsx'), - disabled: exportDisabled, - onClick: showXlsxExportDialog + dropdownOptions: exportOptions, + onSelect: selectExportFormat }, { key: 'drop-all-tables', @@ -434,6 +442,17 @@ function changePageSize(value: number) { void loadTableData(); } +function selectExportFormat(key: string | number) { + if (key === 'csv') { + void showCsvExportDialog(); + return; + } + + if (key === 'xlsx') { + void showXlsxExportDialog(); + } +} + async function showCsvExportDialog() { await loadTables(); if (tables.value.length === 0) { @@ -935,7 +954,7 @@ function formatCell(value: unknown): string { } .table-export-modal { - width: min(520px, calc(100vw - 32px)); + max-width: calc(100vw - 32px); } .export-dialog-body { @@ -952,7 +971,10 @@ function formatCell(value: unknown): string { .export-table-options { max-height: min(420px, 52vh); - padding: 6px 2px; + padding: 8px 10px; + background: var(--td-bg-color-component); + border: 1px solid var(--td-border-color-light); + border-radius: var(--td-radius-default); } .export-dialog-footer { diff --git a/frontend/src/composables/pageHeader.ts b/frontend/src/composables/pageHeader.ts index e57ff66..4006bda 100644 --- a/frontend/src/composables/pageHeader.ts +++ b/frontend/src/composables/pageHeader.ts @@ -1,4 +1,5 @@ import { shallowReactive, unref, type Component, type Ref } from 'vue'; +import type { DropdownOption } from 'naive-ui'; type HeaderValue = T | Ref | (() => T); @@ -12,7 +13,9 @@ export interface PageHeaderAction { variant?: 'solid' | 'outline' | 'text'; loading?: HeaderValue; disabled?: HeaderValue; + dropdownOptions?: HeaderValue; onClick?: () => void | Promise; + onSelect?: (key: string | number, option: DropdownOption) => void | Promise; } export const pageHeader = shallowReactive<{ diff --git a/frontend/src/styles.css b/frontend/src/styles.css index df3e894..298ece9 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -342,6 +342,16 @@ select { white-space: nowrap; } +.header-dropdown-label { + display: inline-flex; + align-items: center; + gap: 4px; +} + +.header-dropdown-caret { + font-size: 13px; +} + .theme-toggle, .restart-btn { display: inline-flex;