feat: 添加数据表字段搜索
This commit is contained in:
@@ -84,7 +84,7 @@
|
||||
<div v-if="selectedTable" class="database-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<span class="current-table">{{ selectedTable }}</span>
|
||||
<span class="row-count">共 {{ total }} 条</span>
|
||||
<span class="row-count">{{ hasActiveSearch ? `匹配 ${total} 条` : `共 ${total} 条` }}</span>
|
||||
</div>
|
||||
<n-space size="small" class="toolbar-actions">
|
||||
<n-button size="small" tertiary :loading="loadingData" @click="reloadTable">刷新</n-button>
|
||||
@@ -95,6 +95,37 @@
|
||||
</n-space>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedTable" class="database-searchbar">
|
||||
<n-select
|
||||
v-model:value="searchField"
|
||||
class="database-search-field"
|
||||
size="small"
|
||||
filterable
|
||||
:options="searchFieldOptions"
|
||||
:consistent-menu-width="false"
|
||||
placeholder="选择字段"
|
||||
/>
|
||||
<n-input
|
||||
v-model:value="searchKeyword"
|
||||
class="database-search-keyword"
|
||||
size="small"
|
||||
clearable
|
||||
placeholder="输入关键字"
|
||||
@keyup.enter="applySearch"
|
||||
@clear="clearSearch"
|
||||
/>
|
||||
<n-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:disabled="!searchField || !searchKeyword.trim()"
|
||||
@click="applySearch"
|
||||
>
|
||||
<template #icon><n-icon><SearchOutline /></n-icon></template>
|
||||
搜索
|
||||
</n-button>
|
||||
<n-button v-if="hasActiveSearch" size="small" tertiary @click="clearSearch">清除</n-button>
|
||||
</div>
|
||||
|
||||
<div class="database-data-body" :class="{ empty: !selectedTable }">
|
||||
<n-empty v-if="!selectedTable" description="请选择左侧的数据表" />
|
||||
<template v-else>
|
||||
@@ -148,7 +179,7 @@
|
||||
</div>
|
||||
|
||||
<div v-if="selectedTable" class="database-pagination">
|
||||
<span class="pagination-total">共 {{ total }} 条</span>
|
||||
<span class="pagination-total">{{ hasActiveSearch ? `匹配 ${total} 条` : `共 ${total} 条` }}</span>
|
||||
<n-pagination
|
||||
:page="page"
|
||||
:page-count="totalPages"
|
||||
@@ -312,6 +343,7 @@ import {
|
||||
ChevronForwardOutline,
|
||||
CloudDownloadOutline,
|
||||
RefreshOutline,
|
||||
SearchOutline,
|
||||
ServerOutline,
|
||||
TrashOutline
|
||||
} from '@vicons/ionicons5';
|
||||
@@ -349,6 +381,10 @@ const rows = ref<RowData[]>([]);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = ref(50);
|
||||
const searchField = ref<string | null>(null);
|
||||
const searchKeyword = ref('');
|
||||
const activeSearchField = ref<string | null>(null);
|
||||
const activeSearchKeyword = ref('');
|
||||
const loadingTables = ref(false);
|
||||
const loadingData = ref(false);
|
||||
const testing = ref(false);
|
||||
@@ -371,6 +407,13 @@ const importInputRef = ref<HTMLInputElement | null>(null);
|
||||
let tableLoadToken = 0;
|
||||
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)));
|
||||
const hasActiveSearch = computed(() => Boolean(activeSearchField.value && activeSearchKeyword.value));
|
||||
const searchFieldOptions = computed(() => (
|
||||
tableInfo.value?.columns
|
||||
.map(column => String(column.Field || ''))
|
||||
.filter(Boolean)
|
||||
.map(field => ({ label: field, value: field })) || []
|
||||
));
|
||||
const selectedDatabaseSourceLabel = computed(() => (
|
||||
databaseSourceOptions.value.find(option => option.value === selectedDatabaseSource.value)?.title || '主数据库'
|
||||
));
|
||||
@@ -547,6 +590,7 @@ function resetTableState() {
|
||||
rows.value = [];
|
||||
total.value = 0;
|
||||
page.value = 1;
|
||||
resetSearchState();
|
||||
columnSchemaExpanded.value = false;
|
||||
loadingTables.value = false;
|
||||
loadingData.value = false;
|
||||
@@ -555,6 +599,7 @@ function resetTableState() {
|
||||
async function loadTable(table: string) {
|
||||
selectedTable.value = table;
|
||||
page.value = 1;
|
||||
resetSearchState();
|
||||
columnSchemaExpanded.value = false;
|
||||
await Promise.all([loadTableInfo(table), loadTableData()]);
|
||||
}
|
||||
@@ -570,6 +615,10 @@ async function loadTableInfo(table: string) {
|
||||
table_name: table,
|
||||
database_source: selectedDatabaseSource.value
|
||||
});
|
||||
const fields = tableInfo.value.columns.map(column => String(column.Field || '')).filter(Boolean);
|
||||
if (!searchField.value || !fields.includes(searchField.value)) {
|
||||
searchField.value = fields[0] || null;
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '加载表结构失败');
|
||||
}
|
||||
@@ -579,11 +628,15 @@ async function loadTableData() {
|
||||
if (!selectedTable.value) return;
|
||||
loadingData.value = true;
|
||||
try {
|
||||
const result = await apiPost<TableData>('/api/database/table/data', {
|
||||
const filters = hasActiveSearch.value && activeSearchField.value
|
||||
? { [activeSearchField.value]: activeSearchKeyword.value }
|
||||
: {};
|
||||
const result = await apiPost<TableData>('/api/database/table/query', {
|
||||
table_name: selectedTable.value,
|
||||
database_source: selectedDatabaseSource.value,
|
||||
page: page.value,
|
||||
page_size: pageSize.value
|
||||
page_size: pageSize.value,
|
||||
filters
|
||||
});
|
||||
rows.value = result.data;
|
||||
total.value = result.total;
|
||||
@@ -594,6 +647,33 @@ async function loadTableData() {
|
||||
}
|
||||
}
|
||||
|
||||
function resetSearchState() {
|
||||
searchField.value = null;
|
||||
searchKeyword.value = '';
|
||||
activeSearchField.value = null;
|
||||
activeSearchKeyword.value = '';
|
||||
}
|
||||
|
||||
function applySearch() {
|
||||
const keyword = searchKeyword.value.trim();
|
||||
if (!searchField.value || !keyword) return;
|
||||
activeSearchField.value = searchField.value;
|
||||
activeSearchKeyword.value = keyword;
|
||||
page.value = 1;
|
||||
void loadTableData();
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
const wasActive = hasActiveSearch.value;
|
||||
searchKeyword.value = '';
|
||||
activeSearchField.value = null;
|
||||
activeSearchKeyword.value = '';
|
||||
if (wasActive) {
|
||||
page.value = 1;
|
||||
void loadTableData();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleColumnSchema() {
|
||||
columnSchemaExpanded.value = !columnSchemaExpanded.value;
|
||||
}
|
||||
@@ -1192,6 +1272,24 @@ async function doImport() {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.database-searchbar {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
background: var(--td-bg-color-container);
|
||||
border-bottom: 1px solid var(--td-border-color-light);
|
||||
}
|
||||
|
||||
.database-search-field {
|
||||
width: min(220px, 28vw);
|
||||
}
|
||||
|
||||
.database-search-keyword {
|
||||
width: min(360px, 38vw);
|
||||
}
|
||||
|
||||
.database-data-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@@ -1451,5 +1549,16 @@ async function doImport() {
|
||||
.toolbar-actions {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.database-searchbar {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.database-search-field,
|
||||
.database-search-keyword {
|
||||
width: auto;
|
||||
min-width: 180px;
|
||||
flex: 1 1 220px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user