From 42e26a52cd204a3c42f37a2437b63027c7bbc896 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Mon, 3 Aug 2026 09:27:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(tdengine):=20=E4=BC=98=E5=8C=96=E5=AD=90?= =?UTF-8?q?=E8=A1=A8=20TAG=20=E5=80=BC=E5=A1=AB=E5=86=99=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 超级表名改为下拉选择,自动从当前库拉取超级表列表 - 选择超级表后自动拉取 TAG 定义,逐行显示名称/类型/输入框 - 根据 TAG 类型自动处理引号拼接 TAGS 值 - 加载失败时降级为文本输入框兜底 - 新增 i18n 中/英/日/德/繁中翻译 --- frontend/src/components/TableDesigner.tsx | 184 ++++++++++++++++++++-- shared/i18n/de-DE.json | 4 + shared/i18n/en-US.json | 4 + shared/i18n/ja-JP.json | 4 + shared/i18n/zh-CN.json | 4 + shared/i18n/zh-TW.json | 4 + 6 files changed, 190 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/TableDesigner.tsx b/frontend/src/components/TableDesigner.tsx index d8e78611..ade9ce0f 100644 --- a/frontend/src/components/TableDesigner.tsx +++ b/frontend/src/components/TableDesigner.tsx @@ -1,6 +1,6 @@ import Modal from './common/ResizableDraggableModal'; import React, { useEffect, useState, useContext, useMemo, useRef, useCallback } from 'react'; -import { Table, Tabs, Button, message, Input, Checkbox, AutoComplete, Tooltip, Select, Empty, Space, Tag, Radio } from 'antd'; +import { Table, Tabs, Button, message, Input, Checkbox, AutoComplete, Tooltip, Select, Empty, Space, Tag, Radio, Spin } from 'antd'; import { ReloadOutlined, SaveOutlined, PlusOutlined, DeleteOutlined, MenuOutlined, FileTextOutlined, EyeOutlined, EditOutlined, ExclamationCircleOutlined, CopyOutlined, TableOutlined } from '@ant-design/icons'; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragOverlay } from '@dnd-kit/core'; import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable'; @@ -8,7 +8,7 @@ import { CSS } from '@dnd-kit/utilities'; import Editor from './MonacoEditor'; import { TabData, ColumnDefinition, IndexDefinition, ForeignKeyDefinition, TriggerDefinition } from '../types'; import { useStore } from '../store'; -import { DBGetColumns, DBGetIndexes, DBQueryAudited, DBGetForeignKeys, DBGetTriggers, DBShowCreateTable } from '../../wailsjs/go/app/App'; +import { DBGetColumns, DBGetIndexes, DBQuery, DBQueryAudited, DBGetForeignKeys, DBGetTriggers, DBShowCreateTable } from '../../wailsjs/go/app/App'; import { hasIndexFormChanged, normalizeIndexFormFromRow, resolveIndexMetadataResponse, shouldRestoreOriginalIndex, toggleIndexSelection as getNextIndexSelection, type IndexDisplaySnapshot } from './tableDesignerIndexUtils'; import { buildIndexCreateSqlPreview } from './tableDesignerIndexSql'; import { buildAlterTablePreviewSql, buildCreateTablePreviewSql, hasAlterTableDraftChanges, type StarRocksCreateTableOptions, type StarRocksDistributionType, type StarRocksKeyModel, type StarRocksTableKind, type TDengineCreateTableOptions, type TDengineTableKind, type TDengineTagDefinition } from './tableDesignerSchemaSql'; @@ -456,6 +456,11 @@ const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, em { _key: 'tag-1', name: 'location', type: 'BINARY(64)' }, ]); const [tdengineTagValues, setTdengineTagValues] = useState(''); + const [tdengineStableOptions, setTdengineStableOptions] = useState<{ label: string; value: string }[]>([]); + const [tdengineStableOptionsLoading, setTdengineStableOptionsLoading] = useState(false); + const [tdengineChildTagDefs, setTdengineChildTagDefs] = useState([]); + const [tdengineChildTagValues, setTdengineChildTagValues] = useState>({}); + const [tdengineChildTagDefsLoading, setTdengineChildTagDefsLoading] = useState(false); const [columnsLoading, setColumnsLoading] = useState(false); const [indexesLoading, setIndexesLoading] = useState(false); @@ -1668,6 +1673,99 @@ ${selectedTrigger.statement}`; } }, [isTDengineNewTable, tab.initialTab]); + useEffect(() => { + if (!isTDengineChildNewTable) { + setTdengineStableOptions([]); + return; + } + let cancelled = false; + const fetchSuperTables = async () => { + setTdengineStableOptionsLoading(true); + try { + const conn = connections.find(c => c.id === tab.connectionId); + if (!conn) return; + const config = { + ...conn.config, + port: Number(conn.config.port), + password: conn.config.password || "", + database: conn.config.database || "", + useSSH: conn.config.useSSH || false, + ssh: conn.config.ssh || { host: "", port: 22, user: "", password: "", keyPath: "" } + }; + const rpcConfig = buildRpcConnectionConfig(config) as any; + const dbName = tab.dbName || ''; + const res = await DBQuery(rpcConfig, dbName, `SHOW STABLES FROM \`${dbName}\``); + if (cancelled) return; + if (res.success && Array.isArray(res.data)) { + const options = res.data.map((row: any) => { + const name = row?.table_name || row?.Table || row?.name || row?.tablename || ''; + return { label: String(name), value: String(name) }; + }).filter(opt => opt.value); + setTdengineStableOptions(options); + } else { + setTdengineStableOptions([]); + } + } catch { + if (!cancelled) setTdengineStableOptions([]); + } finally { + if (!cancelled) setTdengineStableOptionsLoading(false); + } + }; + fetchSuperTables(); + return () => { cancelled = true; }; + }, [isTDengineChildNewTable, tab.connectionId, tab.dbName, connections]); + + useEffect(() => { + if (!isTDengineChildNewTable || !tdengineStableName.trim()) { + setTdengineChildTagDefs([]); + setTdengineChildTagValues({}); + return; + } + let cancelled = false; + const fetchTagDefs = async () => { + setTdengineChildTagDefsLoading(true); + try { + const conn = connections.find(c => c.id === tab.connectionId); + if (!conn) return; + const config = { + ...conn.config, + port: Number(conn.config.port), + password: conn.config.password || "", + database: conn.config.database || "", + useSSH: conn.config.useSSH || false, + ssh: conn.config.ssh || { host: "", port: 22, user: "", password: "", keyPath: "" } + }; + const rpcConfig = buildRpcConnectionConfig(config) as any; + const dbName = tab.dbName || ''; + const res = await DBGetColumns(rpcConfig, dbName, tdengineStableName.trim()); + if (cancelled) return; + if (res.success && Array.isArray(res.data)) { + const tagDefs: TDengineTagDraft[] = res.data + .filter((col: any) => (col?.key || col?.Key || '') === 'TAG') + .map((col: any, idx: number) => ({ + _key: `tagdef-${idx}`, + name: String(col?.name || col?.Name || ''), + type: String(col?.type || col?.Type || ''), + })); + setTdengineChildTagDefs(tagDefs); + setTdengineChildTagValues({}); + } else { + setTdengineChildTagDefs([]); + setTdengineChildTagValues({}); + } + } catch { + if (!cancelled) { + setTdengineChildTagDefs([]); + setTdengineChildTagValues({}); + } + } finally { + if (!cancelled) setTdengineChildTagDefsLoading(false); + } + }; + fetchTagDefs(); + return () => { cancelled = true; }; + }, [isTDengineChildNewTable, tdengineStableName, tab.connectionId, tab.dbName, connections]); + const parseStarRocksRollupOptions = (raw: string): StarRocksCreateTableOptions['rollups'] => ( String(raw || '') .split(/\r?\n/) @@ -1705,11 +1803,23 @@ ${selectedTrigger.statement}`; const buildTDengineCreateOptions = (): TDengineCreateTableOptions | undefined => { if (!isTDengineNewTable) return undefined; + let tagValues = tdengineTagValues; + if (tdengineTableKind === 'child' && tdengineChildTagDefs.length > 0) { + tagValues = tdengineChildTagDefs + .map(tag => { + const val = tdengineChildTagValues[tag.name] ?? ''; + if (!val) return ''; + const needQuote = /^(BINARY|NCHAR|VARBINARY|TIMESTAMP)/i.test(tag.type); + return needQuote ? `'${val.replace(/'/g, "\\'")}'` : val; + }) + .filter(Boolean) + .join(', '); + } return { tableKind: tdengineTableKind, stableName: tdengineStableName, tagDefinitions: tdengineTagDefinitions.map(({ _key, ...tag }) => tag), - tagValues: tdengineTagValues, + tagValues, }; }; @@ -2509,7 +2619,13 @@ END;`; message.error(t('table_designer.message.tdengine_stable_name_required', undefined, i18nLanguage)); return; } - if (!tdengineTagValues.trim()) { + if (tdengineChildTagDefs.length > 0) { + const missing = tdengineChildTagDefs.filter(tag => !(tdengineChildTagValues[tag.name] ?? '').trim()); + if (missing.length > 0) { + message.error(t('table_designer.message.tdengine_tag_values_required', undefined, i18nLanguage)); + return; + } + } else if (!tdengineTagValues.trim()) { message.error(t('table_designer.message.tdengine_tag_values_required', undefined, i18nLanguage)); return; } @@ -2968,19 +3084,59 @@ END;`; {tdengineTableKind === 'child' && ( - setTdengineStableName(event.target.value)} + showSearch + allowClear + value={tdengineStableName || undefined} + onChange={(value) => setTdengineStableName(value || '')} placeholder={t('table_designer.tdengine.placeholder.stable_name', undefined, i18nLanguage)} + options={tdengineStableOptions} + loading={tdengineStableOptionsLoading} + style={{ width: '100%' }} + filterOption={(input, option) => + (option?.label as string ?? '').toLowerCase().includes(input.toLowerCase()) + } + notFoundContent={tdengineStableOptionsLoading ? null : t('table_designer.tdengine.message.no_stable_found', undefined, i18nLanguage)} /> - setTdengineTagValues(event.target.value)} - autoSize={{ minRows: 3, maxRows: 8 }} - placeholder={t('table_designer.tdengine.placeholder.tag_values', undefined, i18nLanguage)} - /> + {tdengineChildTagDefsLoading || tdengineChildTagDefs.length > 0 ? ( + + {tdengineChildTagDefs.length > 0 ? ( + + + {t('table_designer.tdengine.tag.name', undefined, i18nLanguage)} + {t('table_designer.tdengine.tag.type', undefined, i18nLanguage)} + {t('table_designer.tdengine.tag.value', undefined, i18nLanguage)} + + {tdengineChildTagDefs.map((tag) => ( + + {tag.name} + {tag.type} + setTdengineChildTagValues(prev => ({ ...prev, [tag.name]: event.target.value }))} + placeholder={t('table_designer.tdengine.placeholder.tag_value_input', undefined, i18nLanguage)} + style={{ width: 280 }} + /> + + ))} + + ) : ( +
+ {t('table_designer.tdengine.message.loading_tag_defs', undefined, i18nLanguage)} +
+ )} +
+ ) : ( + setTdengineTagValues(event.target.value)} + autoSize={{ minRows: 3, maxRows: 8 }} + placeholder={t('table_designer.tdengine.placeholder.tag_values', undefined, i18nLanguage)} + /> + )}
)} diff --git a/shared/i18n/de-DE.json b/shared/i18n/de-DE.json index 775d9dcc..3a14a35c 100644 --- a/shared/i18n/de-DE.json +++ b/shared/i18n/de-DE.json @@ -8332,6 +8332,10 @@ "table_designer.tdengine.table_kind.stable": "Supertabelle", "table_designer.tdengine.tag.name": "TAG-Name", "table_designer.tdengine.tag.type": "TAG-Typ", + "table_designer.tdengine.tag.value": "TAG-Wert", + "table_designer.tdengine.placeholder.tag_value_input": "TAG-Wert eingeben", + "table_designer.tdengine.message.no_stable_found": "Keine Supertabelle gefunden, bitte erstellen Sie zuerst eine Supertabelle", + "table_designer.tdengine.message.loading_tag_defs": "Lade TAG-Definitionen…", "table_designer.starrocks.bucket_mode.auto": "Eimeranzahl automatisch", "table_designer.starrocks.bucket_mode.number": "Feste Eimeranzahl", "table_designer.starrocks.distribution.hash": "Hash-Verteilung", diff --git a/shared/i18n/en-US.json b/shared/i18n/en-US.json index 4a246b7a..72e71c64 100644 --- a/shared/i18n/en-US.json +++ b/shared/i18n/en-US.json @@ -8332,6 +8332,10 @@ "table_designer.tdengine.table_kind.stable": "Supertable", "table_designer.tdengine.tag.name": "TAG name", "table_designer.tdengine.tag.type": "TAG type", + "table_designer.tdengine.tag.value": "TAG value", + "table_designer.tdengine.placeholder.tag_value_input": "Enter TAG value", + "table_designer.tdengine.message.no_stable_found": "No supertable found, please create a supertable first", + "table_designer.tdengine.message.loading_tag_defs": "Loading TAG definitions…", "table_designer.starrocks.bucket_mode.auto": "Auto buckets", "table_designer.starrocks.bucket_mode.number": "Fixed bucket count", "table_designer.starrocks.distribution.hash": "Hash distribution", diff --git a/shared/i18n/ja-JP.json b/shared/i18n/ja-JP.json index 87c992a1..894bbfc7 100644 --- a/shared/i18n/ja-JP.json +++ b/shared/i18n/ja-JP.json @@ -8332,6 +8332,10 @@ "table_designer.tdengine.table_kind.stable": "スーパーテーブル", "table_designer.tdengine.tag.name": "TAG 名", "table_designer.tdengine.tag.type": "TAG 型", + "table_designer.tdengine.tag.value": "TAG 値", + "table_designer.tdengine.placeholder.tag_value_input": "TAG の値を入力", + "table_designer.tdengine.message.no_stable_found": "スーパーテーブルが見つかりません。最初にスーパーテーブルを作成してください", + "table_designer.tdengine.message.loading_tag_defs": "TAG 定義を読み込み中…", "table_designer.starrocks.bucket_mode.auto": "バケット数を自動設定", "table_designer.starrocks.bucket_mode.number": "固定バケット数", "table_designer.starrocks.distribution.hash": "ハッシュ分散", diff --git a/shared/i18n/zh-CN.json b/shared/i18n/zh-CN.json index 7b9b0f34..32860804 100644 --- a/shared/i18n/zh-CN.json +++ b/shared/i18n/zh-CN.json @@ -8332,6 +8332,10 @@ "table_designer.tdengine.table_kind.stable": "超级表", "table_designer.tdengine.tag.name": "TAG 名称", "table_designer.tdengine.tag.type": "TAG 类型", + "table_designer.tdengine.tag.value": "TAG 值", + "table_designer.tdengine.placeholder.tag_value_input": "请输入 TAG 值", + "table_designer.tdengine.message.no_stable_found": "未找到超级表,请先创建超级表", + "table_designer.tdengine.message.loading_tag_defs": "正在加载 TAG 定义…", "table_designer.starrocks.bucket_mode.auto": "自动分桶数", "table_designer.starrocks.bucket_mode.number": "固定分桶数", "table_designer.starrocks.distribution.hash": "哈希分布", diff --git a/shared/i18n/zh-TW.json b/shared/i18n/zh-TW.json index 567c7fbf..fcfee730 100644 --- a/shared/i18n/zh-TW.json +++ b/shared/i18n/zh-TW.json @@ -8332,6 +8332,10 @@ "table_designer.tdengine.table_kind.stable": "超級表", "table_designer.tdengine.tag.name": "TAG 名稱", "table_designer.tdengine.tag.type": "TAG 類型", + "table_designer.tdengine.tag.value": "TAG 值", + "table_designer.tdengine.placeholder.tag_value_input": "請輸入 TAG 值", + "table_designer.tdengine.message.no_stable_found": "未找到超級表,請先創建超級表", + "table_designer.tdengine.message.loading_tag_defs": "正在加載 TAG 定義…", "table_designer.starrocks.bucket_mode.auto": "自動分桶數", "table_designer.starrocks.bucket_mode.number": "固定分桶數", "table_designer.starrocks.distribution.hash": "雜湊分佈",