feat(grid): show column index indicators in data headers
This commit is contained in:
16 files changed
+272
-5
No files matched your search
@@ -333,6 +333,7 @@ const debugLogCopied = ref(false);
|
||||
const debugLogDownloaded = ref(false);
|
||||
const editShowColumnCommentsInHeader = ref(settingsStore.editorSettings.showColumnCommentsInHeader);
|
||||
const editShowColumnTypesInHeader = ref(settingsStore.editorSettings.showColumnTypesInHeader);
|
||||
const editShowIndexIndicatorsInHeader = ref(settingsStore.editorSettings.showIndexIndicatorsInHeader);
|
||||
const editCompactColumnHeaderActions = ref(settingsStore.editorSettings.compactColumnHeaderActions);
|
||||
const editDataGridQuickEntry = ref(settingsStore.editorSettings.dataGridQuickEntry);
|
||||
const editDataGridAutoTransposeSingleRow = ref(settingsStore.editorSettings.dataGridAutoTransposeSingleRow);
|
||||
@@ -485,6 +486,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
|
||||
tabLayout: editTabLayout.value,
|
||||
showColumnCommentsInHeader: editShowColumnCommentsInHeader.value,
|
||||
showColumnTypesInHeader: editShowColumnTypesInHeader.value,
|
||||
showIndexIndicatorsInHeader: editShowIndexIndicatorsInHeader.value,
|
||||
compactColumnHeaderActions: editCompactColumnHeaderActions.value,
|
||||
dataGridQuickEntry: editDataGridQuickEntry.value,
|
||||
dataGridAutoTransposeSingleRow: editDataGridAutoTransposeSingleRow.value,
|
||||
@@ -752,6 +754,7 @@ function syncEditorSettingsDraftFromStore() {
|
||||
editTabLayout.value = settingsStore.editorSettings.tabLayout;
|
||||
editShowColumnCommentsInHeader.value = settingsStore.editorSettings.showColumnCommentsInHeader;
|
||||
editShowColumnTypesInHeader.value = settingsStore.editorSettings.showColumnTypesInHeader;
|
||||
editShowIndexIndicatorsInHeader.value = settingsStore.editorSettings.showIndexIndicatorsInHeader;
|
||||
editCompactColumnHeaderActions.value = settingsStore.editorSettings.compactColumnHeaderActions;
|
||||
editDataGridQuickEntry.value = settingsStore.editorSettings.dataGridQuickEntry;
|
||||
editDataGridAutoTransposeSingleRow.value = settingsStore.editorSettings.dataGridAutoTransposeSingleRow;
|
||||
@@ -989,6 +992,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
|
||||
} else if (tab === "data") {
|
||||
editShowColumnCommentsInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader;
|
||||
editShowColumnTypesInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnTypesInHeader;
|
||||
editShowIndexIndicatorsInHeader.value = DEFAULT_EDITOR_SETTINGS.showIndexIndicatorsInHeader;
|
||||
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
|
||||
editDataGridQuickEntry.value = DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry;
|
||||
editDataGridAutoTransposeSingleRow.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoTransposeSingleRow;
|
||||
@@ -1052,6 +1056,7 @@ function resetAllDefaults() {
|
||||
editSidebarTablePageSize.value = DEFAULT_SIDEBAR_TABLE_PAGE_SIZE;
|
||||
editShowColumnCommentsInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader;
|
||||
editShowColumnTypesInHeader.value = DEFAULT_EDITOR_SETTINGS.showColumnTypesInHeader;
|
||||
editShowIndexIndicatorsInHeader.value = DEFAULT_EDITOR_SETTINGS.showIndexIndicatorsInHeader;
|
||||
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
|
||||
editDataGridQuickEntry.value = DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry;
|
||||
editDataGridAutoTransposeSingleRow.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoTransposeSingleRow;
|
||||
@@ -4325,6 +4330,17 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<Switch id="show-column-types-in-header" v-model="editShowColumnTypesInHeader" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="show-index-indicators-in-header">
|
||||
{{ t("settings.showIndexIndicatorsInHeader") }}
|
||||
</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t("settings.showIndexIndicatorsInHeaderDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<Switch id="show-index-indicators-in-header" v-model="editShowIndexIndicatorsInHeader" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="compact-column-header-actions">
|
||||
|
||||
@@ -230,6 +230,7 @@ import { getTableMetadataCapabilities } from "@/lib/table/tableMetadataCapabilit
|
||||
import { getTableStructureCapabilities } from "@/lib/table/tableStructureCapabilities";
|
||||
import { filterObjectBrowserTableColumns } from "@/lib/table/objectBrowserTableInfo";
|
||||
import { reserveDataGridHeaderLine } from "@/lib/dataGrid/dataGridHeaderLayout";
|
||||
import { buildColumnIndexMap, columnIndexMetadataRequestCurrent, columnIndexNameKey, columnIndexTableIdentity } from "@/lib/dataGrid/dataGridColumnIndexIcon";
|
||||
import { supportsTableStructureEditing } from "@/lib/database/databaseCapabilities";
|
||||
import { rememberDataGridConditionHistory } from "@/lib/dataGrid/dataGridConditionHistory";
|
||||
import { restoreDataGridLocalColumnFilters, serializeDataGridLocalColumnFilters } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
|
||||
@@ -495,6 +496,15 @@ const dataGridTopbarWidth = ref(0);
|
||||
const dataGridViewportWidth = ref(0);
|
||||
const showColumnCommentsInHeader = computed(() => settingsStore.editorSettings.showColumnCommentsInHeader);
|
||||
const showColumnTypesInHeader = computed(() => settingsStore.editorSettings.showColumnTypesInHeader);
|
||||
const showIndexIndicatorsInHeader = computed(() => settingsStore.editorSettings.showIndexIndicatorsInHeader !== false);
|
||||
const primaryKeyColumnNames = computed(() => {
|
||||
const names = new Set(props.tableMeta?.primaryKeys ?? []);
|
||||
for (const column of props.tableMeta?.columns ?? []) {
|
||||
if (column.is_primary_key) names.add(column.name);
|
||||
}
|
||||
return [...names];
|
||||
});
|
||||
const columnIndexMap = computed(() => buildColumnIndexMap(indexes.value, primaryKeyColumnNames.value));
|
||||
const compactColumnHeaderActions = computed(() => settingsStore.editorSettings.compactColumnHeaderActions);
|
||||
const dataGridRenderMode = computed(() => settingsStore.editorSettings.dataGridRenderMode);
|
||||
const dataGridSearchMode = computed(() => settingsStore.editorSettings.dataGridSearchMode);
|
||||
@@ -7480,6 +7490,16 @@ const indexes = ref<IndexInfo[]>([]);
|
||||
const indexesLoaded = ref(false);
|
||||
const indexesLoading = ref(false);
|
||||
const indexesError = ref("");
|
||||
const currentIndexTableIdentity = computed(() =>
|
||||
columnIndexTableIdentity({
|
||||
connectionId: props.connectionId,
|
||||
database: props.database,
|
||||
catalog: props.tableMeta?.catalog,
|
||||
schema: props.tableMeta?.schema,
|
||||
tableName: props.tableMeta?.tableName,
|
||||
}),
|
||||
);
|
||||
let indexesRequestGeneration = 0;
|
||||
const showDropMongoIndexConfirm = ref(false);
|
||||
const dropMongoIndexLoading = ref(false);
|
||||
const pendingDropMongoIndex = ref<IndexInfo | null>(null);
|
||||
@@ -7673,16 +7693,26 @@ async function fetchDdl() {
|
||||
}
|
||||
|
||||
async function fetchIndexes() {
|
||||
if (!props.connectionId || !props.tableMeta || indexesLoaded.value || indexesLoading.value) return;
|
||||
const requestIdentity = currentIndexTableIdentity.value;
|
||||
if (!props.connectionId || !props.tableMeta || !canShowTableIndexes.value || !requestIdentity || indexesLoaded.value || indexesLoading.value) return;
|
||||
const connectionId = props.connectionId;
|
||||
const database = props.database || "";
|
||||
const schema = props.tableMeta.schema || props.database || "";
|
||||
const tableName = props.tableMeta.tableName;
|
||||
const catalog = props.tableMeta.catalog;
|
||||
const requestGeneration = ++indexesRequestGeneration;
|
||||
indexesLoading.value = true;
|
||||
indexesError.value = "";
|
||||
try {
|
||||
indexes.value = await api.listIndexes(props.connectionId, props.database || "", props.tableMeta.schema || props.database || "", props.tableMeta.tableName, props.tableMeta.catalog);
|
||||
const nextIndexes = await api.listIndexes(connectionId, database, schema, tableName, catalog);
|
||||
if (!columnIndexMetadataRequestCurrent({ requestGeneration, currentGeneration: indexesRequestGeneration, requestIdentity, currentIdentity: currentIndexTableIdentity.value })) return;
|
||||
indexes.value = nextIndexes;
|
||||
indexesLoaded.value = true;
|
||||
} catch (e: any) {
|
||||
if (!columnIndexMetadataRequestCurrent({ requestGeneration, currentGeneration: indexesRequestGeneration, requestIdentity, currentIdentity: currentIndexTableIdentity.value })) return;
|
||||
indexesError.value = String(e?.message || e);
|
||||
} finally {
|
||||
indexesLoading.value = false;
|
||||
if (columnIndexMetadataRequestCurrent({ requestGeneration, currentGeneration: indexesRequestGeneration, requestIdentity, currentIdentity: currentIndexTableIdentity.value })) indexesLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7748,7 +7778,9 @@ watch(
|
||||
ddlContent.value = "";
|
||||
indexes.value = [];
|
||||
indexesLoaded.value = false;
|
||||
indexesLoading.value = false;
|
||||
indexesError.value = "";
|
||||
indexesRequestGeneration += 1;
|
||||
foreignKeys.value = [];
|
||||
foreignKeysLoaded.value = false;
|
||||
foreignKeysLoading.value = false;
|
||||
@@ -7761,6 +7793,14 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [showIndexIndicatorsInHeader.value, canShowTableIndexes.value, currentIndexTableIdentity.value] as const,
|
||||
([showIndicators, canShowIndexes, tableIdentity]) => {
|
||||
if (showIndicators && canShowIndexes && tableIdentity) void fetchIndexes();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// ---- 外键单元格跳转 ----
|
||||
const columnForeignKeyMap = computed(() => buildColumnForeignKeyMap(foreignKeys.value));
|
||||
const foreignKeyNavigationEnabled = computed(() => !!props.connectionId && !!props.tableMeta?.tableName && tableMetadataCapabilities.value.foreignKeys);
|
||||
@@ -8941,6 +8981,11 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
:column-name-label="t('grid.columnName')"
|
||||
:column-type-label="t('grid.columnType')"
|
||||
:column-comment-label="t('grid.columnComment')"
|
||||
:column-index-label="t('grid.tableInfoIndexes')"
|
||||
:column-primary-index-label="t('grid.columnPrimaryIndex')"
|
||||
:column-unique-index-label="t('grid.columnUniqueIndex')"
|
||||
:column-regular-index-label="t('grid.columnRegularIndex')"
|
||||
:column-index-kind="showIndexIndicatorsInHeader ? columnIndexMap.get(columnIndexNameKey(col.name)) : undefined"
|
||||
@pointerdown="startColumnHeaderDrag(col.visibleColIdx, $event)"
|
||||
@click-capture="onHeaderClickCapture"
|
||||
@click="onHeaderClick(col.visibleColIdx, $event)"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties, HTMLAttributes } from "vue";
|
||||
import { Copy } from "@lucide/vue";
|
||||
import { Copy, KeyRound, Hash } from "@lucide/vue";
|
||||
import LightTooltip from "@/components/ui/LightTooltip.vue";
|
||||
import { columnIndexColorClass, type ColumnIndexKind } from "@/lib/dataGrid/dataGridColumnIndexIcon";
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
name: string;
|
||||
actualColumnIndex: number;
|
||||
visibleColumnIndex: number;
|
||||
@@ -26,8 +27,21 @@ defineProps<{
|
||||
columnNameLabel: string;
|
||||
columnTypeLabel: string;
|
||||
columnCommentLabel: string;
|
||||
columnIndexLabel: string;
|
||||
columnPrimaryIndexLabel: string;
|
||||
columnUniqueIndexLabel: string;
|
||||
columnRegularIndexLabel: string;
|
||||
/** 该列的索引类型,undefined 时不显示标识 */
|
||||
columnIndexKind?: ColumnIndexKind;
|
||||
}>();
|
||||
|
||||
function columnIndexText(kind: ColumnIndexKind): string {
|
||||
if (kind === "primary") return props.columnPrimaryIndexLabel;
|
||||
if (kind === "unique") return props.columnUniqueIndexLabel;
|
||||
if (kind === "index") return props.columnRegularIndexLabel;
|
||||
return "";
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
pointerdown: [event: PointerEvent];
|
||||
clickCapture: [event: MouseEvent];
|
||||
@@ -60,6 +74,9 @@ const emit = defineEmits<{
|
||||
@contextmenu="emit('contextmenu', $event)"
|
||||
>
|
||||
<span class="flex min-w-0 items-center gap-1 overflow-hidden">
|
||||
<!-- 索引标识:显示在列名左侧 -->
|
||||
<KeyRound v-if="columnIndexKind === 'primary'" class="h-3 w-3 shrink-0" :class="columnIndexColorClass(columnIndexKind)" :title="columnIndexText(columnIndexKind)" />
|
||||
<Hash v-else-if="columnIndexKind && columnIndexKind !== 'none'" class="h-3 w-3 shrink-0" :class="columnIndexColorClass(columnIndexKind)" :title="columnIndexText(columnIndexKind)" />
|
||||
<span class="flex min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<span class="min-w-0 truncate leading-4">{{ name }}</span>
|
||||
<span v-if="showTypeLine" data-grid-header-type-line class="h-3 min-w-0 truncate text-[10px] font-normal leading-3" :class="[typeClass, { invisible: !columnType }]" :title="columnType || undefined" :aria-hidden="columnType ? undefined : true">{{ columnType }}</span>
|
||||
@@ -88,6 +105,14 @@ const emit = defineEmits<{
|
||||
<span class="text-background/70">{{ columnCommentLabel }}</span>
|
||||
<span>{{ tooltipColumnComment ?? columnComment }}</span>
|
||||
</template>
|
||||
<template v-if="columnIndexKind && columnIndexKind !== 'none'">
|
||||
<span class="text-background/70">{{ columnIndexLabel }}</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<KeyRound v-if="columnIndexKind === 'primary'" class="h-3 w-3" :class="columnIndexColorClass(columnIndexKind)" />
|
||||
<Hash v-else class="h-3 w-3" :class="columnIndexColorClass(columnIndexKind)" />
|
||||
<span :class="columnIndexColorClass(columnIndexKind)">{{ columnIndexText(columnIndexKind) }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</LightTooltip>
|
||||
|
||||
@@ -1526,6 +1526,9 @@ export default {
|
||||
tableInfo: "Table Info",
|
||||
tableInfoColumns: "Columns",
|
||||
tableInfoIndexes: "Indexes",
|
||||
columnPrimaryIndex: "Primary key index",
|
||||
columnUniqueIndex: "Unique index",
|
||||
columnRegularIndex: "Regular index",
|
||||
tableInfoForeignKeys: "Foreign Keys",
|
||||
foreignKeyNavigate: "Go to {table}",
|
||||
tableInfoTriggers: "Triggers",
|
||||
@@ -5013,6 +5016,8 @@ export default {
|
||||
showColumnCommentsInHeaderDescription: "Display table column comments directly below grid column names.",
|
||||
showColumnTypesInHeader: "Show column types under names",
|
||||
showColumnTypesInHeaderDescription: "Display each column's data type directly below grid column names.",
|
||||
showIndexIndicatorsInHeader: "Show index indicators beside column names",
|
||||
showIndexIndicatorsInHeaderDescription: "Use icons to indicate each column's index type in the grid header: primary key (orange), unique index (red), regular index (green).",
|
||||
compactColumnHeaderActions: "Compact column header tools",
|
||||
compactColumnHeaderActionsDescription: "Move formatter and local filter tools into a more menu so column names get priority.",
|
||||
dataGridQuickEntry: "Quick grid entry",
|
||||
|
||||
@@ -1414,6 +1414,9 @@ export default withEnglishFallback({
|
||||
tableInfo: "Información de tabla",
|
||||
tableInfoColumns: "Columnas",
|
||||
tableInfoIndexes: "Índices",
|
||||
columnPrimaryIndex: "Índice de clave primaria",
|
||||
columnUniqueIndex: "Índice único",
|
||||
columnRegularIndex: "Índice normal",
|
||||
tableInfoForeignKeys: "Claves foráneas",
|
||||
foreignKeyNavigate: "Ir a {table}",
|
||||
tableInfoTriggers: "Disparadores",
|
||||
@@ -4790,6 +4793,8 @@ export default withEnglishFallback({
|
||||
showColumnCommentsInHeaderDescription: "Muestra los comentarios de columnas directamente debajo del nombre de la columna.",
|
||||
showColumnTypesInHeader: "Mostrar tipos de columna bajo los nombres",
|
||||
showColumnTypesInHeaderDescription: "Muestra el tipo de dato de cada columna directamente debajo del nombre de la columna.",
|
||||
showIndexIndicatorsInHeader: "Mostrar indicadores de índice junto a los nombres",
|
||||
showIndexIndicatorsInHeaderDescription: "Usa iconos para indicar el tipo de índice de cada columna en el encabezado: clave primaria (naranja), índice único (rojo), índice regular (verde).",
|
||||
compactColumnHeaderActions: "Compactar herramientas del encabezado",
|
||||
compactColumnHeaderActionsDescription: "Mueve formatear y filtrar a un menú de más acciones para priorizar el nombre de columna.",
|
||||
dataGridQuickEntry: "Entrada rápida en grilla",
|
||||
|
||||
@@ -1403,6 +1403,9 @@ export default withEnglishFallback({
|
||||
tableInfo: "Informazioni Tabella",
|
||||
tableInfoColumns: "Colonne",
|
||||
tableInfoIndexes: "Indici",
|
||||
columnPrimaryIndex: "Indice chiave primaria",
|
||||
columnUniqueIndex: "Indice univoco",
|
||||
columnRegularIndex: "Indice normale",
|
||||
tableInfoForeignKeys: "Chiavi Esterne",
|
||||
foreignKeyNavigate: "Vai a {table}",
|
||||
tableInfoTriggers: "Trigger",
|
||||
@@ -4790,6 +4793,8 @@ export default withEnglishFallback({
|
||||
showColumnCommentsInHeaderDescription: "Visualizza i commenti delle colonne della tabella direttamente sotto i nomi delle colonne nella griglia.",
|
||||
showColumnTypesInHeader: "Mostra i tipi di colonna sotto i nomi",
|
||||
showColumnTypesInHeaderDescription: "Visualizza il tipo di dato di ciascuna colonna direttamente sotto i nomi delle colonne nella griglia.",
|
||||
showIndexIndicatorsInHeader: "Mostra gli indicatori di indice accanto ai nomi",
|
||||
showIndexIndicatorsInHeaderDescription: "Utilizza icone per indicare il tipo di indice di ciascuna colonna nell'intestazione: chiave primaria (arancione), indice univoco (rosso), indice normale (verde).",
|
||||
compactColumnHeaderActions: "Strumenti intestazione colonna compatti",
|
||||
compactColumnHeaderActionsDescription: "Sposta gli strumenti del formattatore e dei filtri locali in un menu 'altro' in modo che i nomi delle colonne abbiano la priorità.",
|
||||
dataGridQuickEntry: "Inserimento rapido griglia",
|
||||
|
||||
@@ -1421,6 +1421,9 @@ export default withEnglishFallback({
|
||||
tableInfo: "テーブル情報",
|
||||
tableInfoColumns: "列",
|
||||
tableInfoIndexes: "インデックス",
|
||||
columnPrimaryIndex: "主キーインデックス",
|
||||
columnUniqueIndex: "ユニークインデックス",
|
||||
columnRegularIndex: "通常インデックス",
|
||||
tableInfoForeignKeys: "外部キー",
|
||||
foreignKeyNavigate: "{table} へ移動",
|
||||
tableInfoTriggers: "トリガー",
|
||||
@@ -4827,6 +4830,8 @@ export default withEnglishFallback({
|
||||
showColumnCommentsInHeaderDescription: "グリッド列名の直下にテーブル列コメントを表示します。",
|
||||
showColumnTypesInHeader: "列名の下にデータ型を表示",
|
||||
showColumnTypesInHeaderDescription: "グリッド列名の直下に各列のデータ型を表示します。",
|
||||
showIndexIndicatorsInHeader: "列名の横にインデックス指標を表示",
|
||||
showIndexIndicatorsInHeaderDescription: "グリッド列ヘッダーにインデックスの種類をアイコンで表示します:主キー(オレンジ)、一意インデックス(赤)、通常インデックス(緑)。",
|
||||
compactColumnHeaderActions: "列ヘッダーツールをコンパクトに",
|
||||
compactColumnHeaderActionsDescription: "フォーマッターとローカルフィルターツールをもっと見るメニューに移動し、列名を優先表示します。",
|
||||
dataGridQuickEntry: "グリッド高速入力",
|
||||
|
||||
@@ -1421,6 +1421,9 @@ export default withEnglishFallback({
|
||||
tableInfo: "테이블 정보",
|
||||
tableInfoColumns: "컬럼",
|
||||
tableInfoIndexes: "인덱스",
|
||||
columnPrimaryIndex: "기본 키 인덱스",
|
||||
columnUniqueIndex: "고유 인덱스",
|
||||
columnRegularIndex: "일반 인덱스",
|
||||
tableInfoForeignKeys: "외래 키",
|
||||
foreignKeyNavigate: "{table}(으)로 이동",
|
||||
tableInfoTriggers: "트리거",
|
||||
@@ -4555,6 +4558,8 @@ export default withEnglishFallback({
|
||||
showColumnCommentsInHeaderDescription: "그리드 컬럼 이름 바로 아래에 테이블 컬럼 주석을 표시합니다.",
|
||||
showColumnTypesInHeader: "이름 아래에 컬럼 타입 표시",
|
||||
showColumnTypesInHeaderDescription: "그리드 컬럼 이름 바로 아래에 각 컬럼의 데이터 타입을 표시합니다.",
|
||||
showIndexIndicatorsInHeader: "컬럼 이름 옆에 인덱스 표시",
|
||||
showIndexIndicatorsInHeaderDescription: "그리드 컬럼 헤더에 인덱스 유형을 아이콘으로 표시합니다: 기본키(주황색), 고유 인덱스(빨간색), 일반 인덱스(녹색).",
|
||||
compactColumnHeaderActions: "컴팩트한 컬럼 머리글 도구",
|
||||
compactColumnHeaderActionsDescription: "포매터와 로컬 필터 도구를 더보기 메뉴로 이동하여 컬럼 이름에 우선순위를 둡니다.",
|
||||
dataGridQuickEntry: "빠른 그리드 입력",
|
||||
|
||||
@@ -1405,6 +1405,9 @@ export default withEnglishFallback({
|
||||
tableInfo: "Informações da Tabela",
|
||||
tableInfoColumns: "Colunas",
|
||||
tableInfoIndexes: "Índices",
|
||||
columnPrimaryIndex: "Índice de chave primária",
|
||||
columnUniqueIndex: "Índice exclusivo",
|
||||
columnRegularIndex: "Índice comum",
|
||||
tableInfoForeignKeys: "Chaves Estrangeiras",
|
||||
foreignKeyNavigate: "Ir para {table}",
|
||||
tableInfoTriggers: "Gatilhos",
|
||||
@@ -4792,6 +4795,8 @@ export default withEnglishFallback({
|
||||
showColumnCommentsInHeaderDescription: "Exibir comentários de colunas da tabela diretamente abaixo dos nomes das colunas da grade.",
|
||||
showColumnTypesInHeader: "Mostrar tipos de coluna sob os nomes",
|
||||
showColumnTypesInHeaderDescription: "Exibir o tipo de dado de cada coluna diretamente abaixo dos nomes das colunas da grade.",
|
||||
showIndexIndicatorsInHeader: "Mostrar indicadores de índice ao lado dos nomes",
|
||||
showIndexIndicatorsInHeaderDescription: "Use ícones para indicar o tipo de índice de cada coluna no cabeçalho: chave primária (laranja), índice exclusivo (vermelho), índice regular (verde).",
|
||||
compactColumnHeaderActions: "Ferramentas compactas no cabeçalho da coluna",
|
||||
compactColumnHeaderActionsDescription: "Mover as ferramentas de formatação e filtro local para um menu de mais opções para que os nomes das colunas tenham prioridade.",
|
||||
dataGridQuickEntry: "Entrada rápida na grade",
|
||||
|
||||
@@ -1527,6 +1527,9 @@ export default withEnglishFallback({
|
||||
tableInfo: "表属性",
|
||||
tableInfoColumns: "字段",
|
||||
tableInfoIndexes: "索引",
|
||||
columnPrimaryIndex: "主键索引",
|
||||
columnUniqueIndex: "唯一索引",
|
||||
columnRegularIndex: "普通索引",
|
||||
tableInfoForeignKeys: "外键",
|
||||
foreignKeyNavigate: "跳转到 {table}",
|
||||
tableInfoTriggers: "触发器",
|
||||
@@ -5011,6 +5014,8 @@ export default withEnglishFallback({
|
||||
showColumnCommentsInHeaderDescription: "把表字段注释直接显示在结果表头字段名下方。",
|
||||
showColumnTypesInHeader: "在字段名下方显示数据类型",
|
||||
showColumnTypesInHeaderDescription: "把每个字段的数据类型直接显示在结果表头字段名下方。",
|
||||
showIndexIndicatorsInHeader: "在字段名旁显示索引标识",
|
||||
showIndexIndicatorsInHeaderDescription: "在结果表头字段名旁用图标标识字段的索引类型:主键(橙色)、唯一索引(红色)、普通索引(绿色)。",
|
||||
compactColumnHeaderActions: "收起字段表头工具",
|
||||
compactColumnHeaderActionsDescription: "将格式化、本地筛选收进更多菜单,优先显示字段名称。",
|
||||
dataGridQuickEntry: "表格快捷录入",
|
||||
|
||||
@@ -1405,6 +1405,9 @@ export default withEnglishFallback({
|
||||
tableInfo: "資料表屬性",
|
||||
tableInfoColumns: "欄位",
|
||||
tableInfoIndexes: "索引",
|
||||
columnPrimaryIndex: "主鍵索引",
|
||||
columnUniqueIndex: "唯一索引",
|
||||
columnRegularIndex: "一般索引",
|
||||
tableInfoForeignKeys: "外鍵",
|
||||
foreignKeyNavigate: "跳轉到 {table}",
|
||||
tableInfoTriggers: "觸發器",
|
||||
@@ -4253,6 +4256,8 @@ export default withEnglishFallback({
|
||||
showColumnCommentsInHeaderDescription: "直接在資料表格欄位名稱下方顯示資料表欄位註解。",
|
||||
showColumnTypesInHeader: "在欄位名稱下方顯示資料類型",
|
||||
showColumnTypesInHeaderDescription: "直接在資料表格欄位名稱下方顯示每個欄位的資料類型。",
|
||||
showIndexIndicatorsInHeader: "在欄位名稱旁顯示索引標識",
|
||||
showIndexIndicatorsInHeaderDescription: "在資料表格欄位名稱旁用圖示標識欄位的索引類型:主鍵(橙色)、唯一索引(紅色)、普通索引(綠色)。",
|
||||
compactColumnHeaderActions: "收起欄位表頭工具",
|
||||
compactColumnHeaderActionsDescription: "將格式設定和本機篩選工具移到更多選單,讓欄位名稱優先顯示。",
|
||||
dataGridQuickEntry: "表格快速輸入",
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildColumnIndexMap, columnIndexMetadataRequestCurrent, columnIndexNameKey, columnIndexTableIdentity } from "@/lib/dataGrid/dataGridColumnIndexIcon";
|
||||
import type { IndexInfo } from "@/types/database";
|
||||
|
||||
function indexInfo(overrides: Partial<IndexInfo>): IndexInfo {
|
||||
return {
|
||||
name: "idx_users_email",
|
||||
columns: ["email"],
|
||||
is_unique: false,
|
||||
is_primary: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("buildColumnIndexMap", () => {
|
||||
it("uses column metadata for custom-named and composite primary keys", () => {
|
||||
const map = buildColumnIndexMap([indexInfo({ name: "PK_USERS", columns: ["User_ID", "Tenant_ID"], is_unique: true }), indexInfo({ columns: ["user_id"] })], ["user_id", "tenant_id"]);
|
||||
|
||||
expect(map.get(columnIndexNameKey("USER_ID"))).toBe("primary");
|
||||
expect(map.get(columnIndexNameKey("tenant_id"))).toBe("primary");
|
||||
});
|
||||
|
||||
it("keeps the highest-priority kind with case-insensitive names", () => {
|
||||
const map = buildColumnIndexMap([indexInfo({ columns: ["Email"] }), indexInfo({ name: "uq_email", columns: ["email"], is_unique: true })]);
|
||||
expect(map.get(columnIndexNameKey("EMAIL"))).toBe("unique");
|
||||
});
|
||||
});
|
||||
|
||||
describe("column index metadata request guard", () => {
|
||||
it("includes the full table identity and rejects stale responses", () => {
|
||||
const users = columnIndexTableIdentity({
|
||||
connectionId: "c1",
|
||||
database: "db",
|
||||
schema: "public",
|
||||
tableName: "users",
|
||||
})!;
|
||||
const orders = columnIndexTableIdentity({
|
||||
connectionId: "c1",
|
||||
database: "db",
|
||||
schema: "public",
|
||||
tableName: "orders",
|
||||
})!;
|
||||
|
||||
expect(users).not.toBe(orders);
|
||||
expect(
|
||||
columnIndexMetadataRequestCurrent({
|
||||
requestGeneration: 2,
|
||||
currentGeneration: 2,
|
||||
requestIdentity: users,
|
||||
currentIdentity: users,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
columnIndexMetadataRequestCurrent({
|
||||
requestGeneration: 2,
|
||||
currentGeneration: 3,
|
||||
requestIdentity: users,
|
||||
currentIdentity: users,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
columnIndexMetadataRequestCurrent({
|
||||
requestGeneration: 2,
|
||||
currentGeneration: 2,
|
||||
requestIdentity: users,
|
||||
currentIdentity: orders,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { IndexInfo } from "@/types/database";
|
||||
|
||||
/** 列的索引类型分类 */
|
||||
export type ColumnIndexKind = "primary" | "unique" | "index" | "none";
|
||||
|
||||
export function columnIndexNameKey(name: string): string {
|
||||
return name.trim().toLowerCase();
|
||||
}
|
||||
|
||||
/** 从主键和索引列表构建规范化列名 → 最高优先级索引类型的映射 */
|
||||
export function buildColumnIndexMap(indexes: IndexInfo[], primaryKeyColumns: readonly string[] = []): Map<string, ColumnIndexKind> {
|
||||
const map = new Map<string, ColumnIndexKind>();
|
||||
for (const column of primaryKeyColumns) {
|
||||
const key = columnIndexNameKey(column);
|
||||
if (key) map.set(key, "primary");
|
||||
}
|
||||
for (const idx of indexes) {
|
||||
const kind: ColumnIndexKind = idx.is_primary ? "primary" : idx.is_unique ? "unique" : "index";
|
||||
for (const col of idx.columns) {
|
||||
const key = columnIndexNameKey(col);
|
||||
if (!key) continue;
|
||||
const existing = map.get(key);
|
||||
if (!existing || columnIndexPriority(kind) > columnIndexPriority(existing)) {
|
||||
map.set(key, kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export function columnIndexTableIdentity(options: { connectionId?: string; database?: string; catalog?: string; schema?: string; tableName?: string }): string | undefined {
|
||||
if (!options.connectionId || !options.tableName) return undefined;
|
||||
return JSON.stringify([options.connectionId, options.database ?? "", options.catalog ?? "", options.schema ?? "", options.tableName]);
|
||||
}
|
||||
|
||||
export function columnIndexMetadataRequestCurrent(options: { requestGeneration: number; currentGeneration: number; requestIdentity: string; currentIdentity?: string }): boolean {
|
||||
return options.requestGeneration === options.currentGeneration && options.requestIdentity === options.currentIdentity;
|
||||
}
|
||||
|
||||
function columnIndexPriority(kind: ColumnIndexKind): number {
|
||||
switch (kind) {
|
||||
case "primary":
|
||||
return 3;
|
||||
case "unique":
|
||||
return 2;
|
||||
case "index":
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** 索引类型对应的颜色类名(橙色=主键,红色=唯一,绿色=普通) */
|
||||
export function columnIndexColorClass(kind: ColumnIndexKind): string {
|
||||
switch (kind) {
|
||||
case "primary":
|
||||
return "text-orange-500";
|
||||
case "unique":
|
||||
return "text-red-500";
|
||||
case "index":
|
||||
return "text-green-500";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
|
||||
"tabLayout",
|
||||
"showColumnCommentsInHeader",
|
||||
"showColumnTypesInHeader",
|
||||
"showIndexIndicatorsInHeader",
|
||||
"compactColumnHeaderActions",
|
||||
"dataGridQuickEntry",
|
||||
"dataGridAutoTransposeSingleRow",
|
||||
|
||||
@@ -168,6 +168,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
|
||||
{ id: "data-page-size", category: "data", titleKey: "settings.tableOpenPageSize", descriptionKey: "settings.tableOpenPageSizeDescription", targetId: "data" },
|
||||
{ id: "appearance-header-comments", category: "appearance", titleKey: "settings.showColumnCommentsInHeader", descriptionKey: "settings.showColumnCommentsInHeaderDescription", targetId: "appearance" },
|
||||
{ id: "appearance-header-types", category: "appearance", titleKey: "settings.showColumnTypesInHeader", descriptionKey: "settings.showColumnTypesInHeaderDescription", targetId: "appearance" },
|
||||
{ id: "appearance-index-indicators", category: "appearance", titleKey: "settings.showIndexIndicatorsInHeader", descriptionKey: "settings.showIndexIndicatorsInHeaderDescription", targetId: "appearance" },
|
||||
{ id: "appearance-compact-header-actions", category: "appearance", titleKey: "settings.compactColumnHeaderActions", descriptionKey: "settings.compactColumnHeaderActionsDescription", targetId: "appearance" },
|
||||
{ id: "appearance-auto-total", category: "appearance", titleKey: "settings.autoCalculateTotalRows", descriptionKey: "settings.autoCalculateTotalRowsDescription", targetId: "appearance" },
|
||||
{ id: "appearance-infinite-scroll", category: "appearance", titleKey: "settings.infiniteScroll", descriptionKey: "settings.infiniteScrollDescription", targetId: "appearance" },
|
||||
|
||||
@@ -469,6 +469,7 @@ export interface EditorSettings {
|
||||
mongoViewMode: "document" | "table";
|
||||
showColumnCommentsInHeader: boolean;
|
||||
showColumnTypesInHeader: boolean;
|
||||
showIndexIndicatorsInHeader: boolean;
|
||||
compactColumnHeaderActions: boolean;
|
||||
columnWidthDensity: ColumnWidthDensity;
|
||||
dataGridQuickEntry: boolean;
|
||||
@@ -647,6 +648,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
|
||||
mongoViewMode: "document",
|
||||
showColumnCommentsInHeader: true,
|
||||
showColumnTypesInHeader: true,
|
||||
showIndexIndicatorsInHeader: true,
|
||||
compactColumnHeaderActions: true,
|
||||
columnWidthDensity: "standard",
|
||||
dataGridQuickEntry: false,
|
||||
@@ -956,6 +958,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
|
||||
mongoViewMode: settings.mongoViewMode === "table" ? "table" : DEFAULT_EDITOR_SETTINGS.mongoViewMode,
|
||||
showColumnCommentsInHeader: settings.showColumnCommentsInHeader ?? DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader,
|
||||
showColumnTypesInHeader: settings.showColumnTypesInHeader ?? DEFAULT_EDITOR_SETTINGS.showColumnTypesInHeader,
|
||||
showIndexIndicatorsInHeader: settings.showIndexIndicatorsInHeader ?? DEFAULT_EDITOR_SETTINGS.showIndexIndicatorsInHeader,
|
||||
compactColumnHeaderActions: settings.compactColumnHeaderActions ?? DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions,
|
||||
columnWidthDensity: normalizeColumnWidthDensity(settings.columnWidthDensity),
|
||||
dataGridQuickEntry: settings.dataGridQuickEntry ?? DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry,
|
||||
@@ -1449,6 +1452,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
||||
if (partial.mongoViewMode !== undefined) editorSettings.value.mongoViewMode = partial.mongoViewMode;
|
||||
if (partial.showColumnCommentsInHeader !== undefined) editorSettings.value.showColumnCommentsInHeader = partial.showColumnCommentsInHeader;
|
||||
if (partial.showColumnTypesInHeader !== undefined) editorSettings.value.showColumnTypesInHeader = partial.showColumnTypesInHeader;
|
||||
if (partial.showIndexIndicatorsInHeader !== undefined) editorSettings.value.showIndexIndicatorsInHeader = partial.showIndexIndicatorsInHeader;
|
||||
if (partial.compactColumnHeaderActions !== undefined) editorSettings.value.compactColumnHeaderActions = partial.compactColumnHeaderActions;
|
||||
if (partial.columnWidthDensity !== undefined) editorSettings.value.columnWidthDensity = normalizeColumnWidthDensity(partial.columnWidthDensity);
|
||||
if (partial.dataGridQuickEntry !== undefined) editorSettings.value.dataGridQuickEntry = partial.dataGridQuickEntry;
|
||||
|
||||
Reference in new issue
Block a user