>();
+ const syncDialogVisible = ref(false);
+ const refreshingPhysicalStatus = ref(false);
+
const ensureDatasourceId = async () => {
if (datasourceId.value !== null) {
return datasourceId.value;
@@ -198,6 +204,49 @@
}
};
+ const handleOpenSync = async () => {
+ const activeDatasourceId = await ensureDatasourceId();
+ if (activeDatasourceId === null) {
+ return;
+ }
+ syncDialogVisible.value = true;
+ };
+
+ const handleRefreshPhysicalStatus = async () => {
+ const activeDatasourceId = await ensureDatasourceId();
+ if (activeDatasourceId === null) {
+ return;
+ }
+ refreshingPhysicalStatus.value = true;
+ try {
+ const response = await refreshPhysicalStatus(activeDatasourceId);
+ ElMessage.success(buildSyncSummary(response.data.data, physicalStatusSyncSummaryFields));
+ await loadPage();
+ if (columnDrawerVisible.value && selectedTableForColumns.value && columnManageRef.value) {
+ await columnManageRef.value.handleTableChange(selectedTableForColumns.value);
+ }
+ } catch (err) {
+ ElMessage.error((err as Error).message);
+ } finally {
+ refreshingPhysicalStatus.value = false;
+ }
+ };
+
+ const handleSyncCompleted = async (selectedTableNames: string[]) => {
+ await loadPage();
+ const normalizedSelectedTableNameSet = new Set(
+ selectedTableNames.map(tableName => tableName.trim().toLowerCase()),
+ );
+ if (
+ columnDrawerVisible.value &&
+ selectedTableForColumns.value &&
+ normalizedSelectedTableNameSet.has(selectedTableForColumns.value.trim().toLowerCase()) &&
+ columnManageRef.value
+ ) {
+ await columnManageRef.value.handleTableChange(selectedTableForColumns.value);
+ }
+ };
+
defineExpose({
loadPage,
});
@@ -214,7 +263,13 @@
表语义列表
管理和维护物理表的业务语义信息。
- 共 {{ page.total }} 张表
+
@@ -241,6 +296,13 @@
+
+
+
+ {{ row.hasPhysicalTable ? '存在' : '缺失' }}
+
+
+
@@ -333,6 +395,12 @@
:table-name="selectedTableForColumns"
/>
+
+
@@ -345,6 +413,12 @@
margin-bottom: 20px;
}
+ .header-actions {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ }
+
.section-header h3 {
font-size: 16px;
color: var(--app-text-primary);
diff --git a/data-agent-frontend/src/views/semantic/relation/types.ts b/data-agent-frontend/src/views/semantic/types.ts
similarity index 77%
rename from data-agent-frontend/src/views/semantic/relation/types.ts
rename to data-agent-frontend/src/views/semantic/types.ts
index 5e3516a..fdad6ff 100644
--- a/data-agent-frontend/src/views/semantic/relation/types.ts
+++ b/data-agent-frontend/src/views/semantic/types.ts
@@ -15,11 +15,6 @@
* along with this program. If not, see .
*/
-import type {
- RelationCandidateColumnResponse,
- RelationCandidateTableResponse,
-} from '@/api/semantic';
-
export interface RelationForm {
sourceTableName: string;
sourceColumnNames: string[];
@@ -37,12 +32,29 @@ export interface RelationDraftPreview {
enabled: boolean;
}
-export interface TableNodeLayout extends RelationCandidateTableResponse {
+export interface RelationTableNode {
+ tableName: string;
+ domain: string | null;
+ description: string | null;
+ operable: boolean;
+ invalidReason: string | null;
+}
+
+export interface RelationColumnNode {
+ columnName: string;
+ description: string | null;
+ typeName: string | null;
+ primaryKey: boolean | null;
+ operable: boolean;
+ invalidReason: string | null;
+}
+
+export interface TableNodeLayout extends RelationTableNode {
x: number;
y: number;
width: number;
height: number;
- columns: RelationCandidateColumnResponse[];
+ columns: RelationColumnNode[];
}
export interface RelationViewportState {
diff --git a/data-agent-frontend/src/views/semantic/utils.ts b/data-agent-frontend/src/views/semantic/utils.ts
new file mode 100644
index 0000000..da83919
--- /dev/null
+++ b/data-agent-frontend/src/views/semantic/utils.ts
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import type { SyncTableSemanticsResponse } from '@/api/semantic';
+
+export type SyncSummaryField = keyof Omit;
+
+const syncSummaryFieldLabels: Record = {
+ addedTables: { label: '新增表', unit: '张' },
+ reactivatedTables: { label: '恢复表', unit: '张' },
+ updatedTables: { label: '更新表', unit: '张' },
+ missingTablesMarked: { label: '标记缺失表', unit: '张' },
+ addedColumns: { label: '新增列', unit: '个' },
+ reactivatedColumns: { label: '恢复列', unit: '个' },
+ updatedColumns: { label: '更新列', unit: '个' },
+ missingColumnsMarked: { label: '标记缺失列', unit: '个' },
+};
+
+export const allSyncSummaryFields: SyncSummaryField[] = [
+ 'addedTables',
+ 'reactivatedTables',
+ 'updatedTables',
+ 'missingTablesMarked',
+ 'addedColumns',
+ 'reactivatedColumns',
+ 'updatedColumns',
+ 'missingColumnsMarked',
+];
+
+export const physicalStatusSyncSummaryFields: SyncSummaryField[] = [
+ 'missingTablesMarked',
+ 'missingColumnsMarked',
+];
+
+export const buildSyncSummary = (
+ result: SyncTableSemanticsResponse,
+ fields: readonly SyncSummaryField[] = allSyncSummaryFields,
+) =>
+ fields
+ .map(field => {
+ const { label, unit } = syncSummaryFieldLabels[field];
+ return `${label} ${result[field]} ${unit}`;
+ })
+ .join(',');
diff --git a/sql/data_source.sql b/sql/data_source.sql
index 86391a8..5b93723 100644
--- a/sql/data_source.sql
+++ b/sql/data_source.sql
@@ -20,10 +20,12 @@ CREATE TABLE IF NOT EXISTS `datasource` (
CREATE TABLE IF NOT EXISTS `table_info` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`table_name` VARCHAR(255) NOT NULL COMMENT '表名',
+ `physical_table_description` VARCHAR(500) DEFAULT NULL COMMENT '物理表原始描述',
`table_description` VARCHAR(500) DEFAULT NULL COMMENT '表描述',
`domain` VARCHAR(255) DEFAULT NULL COMMENT '领域',
`datasource_id` INT NOT NULL COMMENT '关联数据源ID',
`is_visible` TINYINT(1) DEFAULT 1 COMMENT '是否可见',
+ `physical_status` TINYINT(1) DEFAULT 1 COMMENT '物理表是否存在',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
@@ -39,8 +41,12 @@ CREATE TABLE IF NOT EXISTS `column_info` (
`datasource_id` INT NOT NULL COMMENT '关联数据源ID',
`table_name` VARCHAR(255) NOT NULL COMMENT '表名',
`column_name` VARCHAR(255) NOT NULL COMMENT '列名',
+ `physical_column_description` VARCHAR(500) DEFAULT NULL COMMENT '物理列原始描述',
+ `type_name` VARCHAR(255) DEFAULT NULL COMMENT '物理列类型',
+ `primary_key` TINYINT(1) DEFAULT NULL COMMENT '是否物理主键',
`column_description` VARCHAR(500) DEFAULT NULL COMMENT '列描述',
`is_visible` TINYINT(1) DEFAULT 1 COMMENT '是否可见',
+ `physical_status` TINYINT(1) DEFAULT 1 COMMENT '物理列是否存在',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),