diff --git a/seatunnel-ui/src/locales/en_US/project.ts b/seatunnel-ui/src/locales/en_US/project.ts index c3af0f871..77a1ec808 100644 --- a/seatunnel-ui/src/locales/en_US/project.ts +++ b/seatunnel-ui/src/locales/en_US/project.ts @@ -1098,6 +1098,7 @@ export default { sql_content_label: 'SQL', sql_content_label_placeholder: 'please input the SQL statement', query_validate: 'please input the SQL statement', + target_name_tips: 'Please enter or select table name' }, synchronization_instance: { pipeline_id: 'Pipeline Id', diff --git a/seatunnel-ui/src/locales/zh_CN/project.ts b/seatunnel-ui/src/locales/zh_CN/project.ts index 4f09acaa8..774b6187e 100644 --- a/seatunnel-ui/src/locales/zh_CN/project.ts +++ b/seatunnel-ui/src/locales/zh_CN/project.ts @@ -1065,7 +1065,8 @@ export default { check_model: '请检查模型信息', sql_content_label: 'SQL', sql_content_label_placeholder: '请输入SQL语句', - query_validate: '请输入SQL语句' + query_validate: '请输入SQL语句', + target_name_tips: '请输入或选择表名(必填)' }, synchronization_instance: { pipeline_id: 'Pipeline ID', @@ -1110,7 +1111,7 @@ export default { confirm: '确定', cancel: '取消', delete: '删除', - delete_confirm: '确定删除吗?', + delete_confirm: '确定删除吗?' }, menu: { fav: '收藏组件', diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/configuration-form.tsx b/seatunnel-ui/src/views/task/synchronization-definition/dag/configuration-form.tsx index 2554870cf..97583ff73 100644 --- a/seatunnel-ui/src/views/task/synchronization-definition/dag/configuration-form.tsx +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/configuration-form.tsx @@ -36,8 +36,7 @@ import { getSceneModeOptions } from './use-configuration-form' import { useI18n } from 'vue-i18n' -import type { NodeType } from './types' - +import type { NodeType, TableOption, State } from './types' import { debounce } from 'lodash' const ConfigurationForm = defineComponent({ @@ -75,16 +74,55 @@ const ConfigurationForm = defineComponent({ const onTableChange = (tableName: any) => { state.model.tableName = tableName + if (props.nodeType === 'sink' && state.model.database) { + getTableOptions(state.model.database, '') + } emit('tableNameChange', state.model) } - - const prevQueryTableName = ref(''); - const onTableSearch = debounce((tableName: any) => { - // rely on database - if(state.model.database && prevQueryTableName.value !== tableName) { - getTableOptions(state.model.database, tableName) - prevQueryTableName.value = tableName + const prevQueryTableName = ref('') + const onTableSearch = debounce(async (tableName: any) => { + // If it is a sink node and there is input content. + if (props.nodeType === 'sink' && tableName) { + try { + // rely on database + if (state.model.database && prevQueryTableName.value !== tableName) { + await getTableOptions(state.model.database, tableName) + prevQueryTableName.value = tableName + + // If there are no results after searching, add user input as a custom value to the options + const existingOption = state.tableOptions.find( + (option: TableOption) => option.value === tableName + ) + + if (!existingOption) { + const newOption: TableOption = { + label: tableName, + value: tableName + } + state.tableOptions = [...state.tableOptions, newOption] + } + } + } catch (err) { + // If the interface call fails, also use user input as a custom value + const existingOption = state.tableOptions.find( + (option: TableOption) => option.value === tableName + ) + + if (!existingOption) { + const newOption: TableOption = { + label: tableName, + value: tableName + } + state.tableOptions = [...state.tableOptions, newOption] + } + } + } else { + // The source node maintains its original logic + if (state.model.database && prevQueryTableName.value !== tableName) { + getTableOptions(state.model.database, tableName) + prevQueryTableName.value = tableName + } } }, 1000) @@ -214,7 +252,12 @@ const ConfigurationForm = defineComponent({ onSearch={onTableSearch} remote virtualScroll - /> + clearable + tag={props.nodeType === 'sink'} + showArrow={true} + allowInput={props.nodeType === 'sink'} + placeholder={t('project.synchronization_definition.target_name_tips')} + /> )} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/types.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/types.ts index 3ebc7c433..a9028289e 100644 --- a/seatunnel-ui/src/views/task/synchronization-definition/dag/types.ts +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/types.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +import type { SelectOption } from 'naive-ui' + export type NodeType = 'source' | 'sink' | 'transform' export type OptionType = 'datasource' | 'database' | 'table' export type { TableColumns } from 'naive-ui/es/data-table/src/interface' @@ -59,3 +61,23 @@ export type InputPlugin = { type: NodeType } export type NodeInfo = { [key: string]: any } + +export interface TableOption { + label: string; + value: string; +} + +export interface State { + model: any; + rules: any; + loading: boolean; + tableLoading: boolean; + databaseLoading: boolean; + datasourceLoading: boolean; + formStructure: any[]; + formName: string; + formLocales: any; + datasourceOptions: SelectOption[]; + databaseOptions: SelectOption[]; + tableOptions: TableOption[]; +} \ No newline at end of file diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/use-configuration-form.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/use-configuration-form.ts index 5252d7d86..d4340bbc7 100644 --- a/seatunnel-ui/src/views/task/synchronization-definition/dag/use-configuration-form.ts +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/use-configuration-form.ts @@ -32,7 +32,8 @@ import { findSink } from '@/service/sync-task-definition' import { useSynchronizationDefinitionStore } from '@/store/synchronization-definition' -import type { NodeType } from './types' +import type { NodeType, TableOption, State } from './types' +import type { SelectOption } from 'naive-ui' export const useConfigurationForm = ( nodeType: NodeType, @@ -56,7 +57,24 @@ export const useConfigurationForm = ( query: '' } - const state = reactive({ + const state = reactive<{ + model: typeof initialModel; + loading: boolean; + datasourceOptions: any[]; + datasourceLoading: boolean; + databaseOptions: any[]; + databaseLoading: boolean; + tableOptions: TableOption[]; + tableLoading: boolean; + formStructure: any[]; + formLocales: any; + formName: string; + formLoading: boolean; + inputTableData: any[]; + outputTableData: any[]; + tableColumnsLoading: boolean; + rules: any; + }>({ model: cloneDeep(initialModel), loading: false, datasourceOptions: [],