This is an automated email from the ASF dual-hosted git repository.

songjian pushed a commit to branch add_canvas_job_define
in repository https://gitbox.apache.org/repos/asf/seatunnel-web.git


The following commit(s) were added to refs/heads/add_canvas_job_define by this 
push:
     new 153e7b4f [Feat][UI] Add virtual table function.
153e7b4f is described below

commit 153e7b4f7e0124092fdf8f4a765d0aad6f580cf0
Author: songjianet <1778651...@qq.com>
AuthorDate: Wed Jun 7 09:38:58 2023 +0800

    [Feat][UI] Add virtual table function.
---
 .../src/layouts/dashboard/header/menu/use-menu.ts  |   4 +
 seatunnel-ui/src/locales/en_US/datasource.ts       |   1 +
 seatunnel-ui/src/locales/en_US/index.ts            |   4 +-
 seatunnel-ui/src/locales/en_US/menu.ts             |   3 +-
 seatunnel-ui/src/locales/en_US/virtual-tables.ts   |  63 ++++
 seatunnel-ui/src/locales/zh_CN/datasource.ts       |   1 +
 seatunnel-ui/src/locales/zh_CN/index.ts            |   4 +-
 seatunnel-ui/src/locales/zh_CN/menu.ts             |   3 +-
 seatunnel-ui/src/locales/zh_CN/virtual-tables.ts   |  62 ++++
 seatunnel-ui/src/router/routes.ts                  |   4 +-
 .../zh_CN/index.ts => router/virtual-tables.ts}    |  42 +--
 .../menu.ts => service/virtual-tables/index.ts}    |  17 +-
 .../menu.ts => service/virtual-tables/types.ts}    |  11 -
 seatunnel-ui/src/views/datasource/list/index.tsx   |   2 +-
 .../src/views/datasource/list/use-columns.ts       |   2 +
 seatunnel-ui/src/views/virtual-tables/detail.tsx   | 198 +++++++++++++
 .../src/views/virtual-tables/index.module.scss     |  81 +++++
 .../src/views/virtual-tables/list/index.tsx        | 120 ++++++++
 .../src/views/virtual-tables/list/use-columns.ts   | 115 ++++++++
 .../src/views/virtual-tables/list/use-table.ts     | 114 +++++++
 .../src/views/virtual-tables/step-one-form.tsx     | 144 +++++++++
 .../virtual-tables/step-three-params.tsx}          |  55 ++--
 .../src/views/virtual-tables/step-two-form.tsx     | 117 ++++++++
 .../src/views/virtual-tables/step-two-table.tsx    | 327 +++++++++++++++++++++
 .../menu.ts => views/virtual-tables/types.ts}      |  19 +-
 .../src/views/virtual-tables/use-detail.ts         | 184 ++++++++++++
 26 files changed, 1619 insertions(+), 78 deletions(-)

diff --git a/seatunnel-ui/src/layouts/dashboard/header/menu/use-menu.ts 
b/seatunnel-ui/src/layouts/dashboard/header/menu/use-menu.ts
index ebdc7994..509bc8b5 100644
--- a/seatunnel-ui/src/layouts/dashboard/header/menu/use-menu.ts
+++ b/seatunnel-ui/src/layouts/dashboard/header/menu/use-menu.ts
@@ -39,6 +39,10 @@ export function useMenu() {
       label: () => h(NEllipsis, null, { default: () => t('menu.datasource') }),
       key: 'datasource'
     },
+    {
+      label: () => h(NEllipsis, null, { default: () => 
t('menu.virtual_tables') }),
+      key: 'virtual-tables'
+    },
     {
       label: () => h(NEllipsis, null, { default: () => t('menu.user_manage') 
}),
       key: 'user-manage'
diff --git a/seatunnel-ui/src/locales/en_US/datasource.ts 
b/seatunnel-ui/src/locales/en_US/datasource.ts
index 6edf737c..e811af28 100644
--- a/seatunnel-ui/src/locales/en_US/datasource.ts
+++ b/seatunnel-ui/src/locales/en_US/datasource.ts
@@ -16,6 +16,7 @@
  */
 
 export default {
+  id: 'Id',
   datasource: 'DataSource',
   create_datasource: 'Create DataSource',
   choose_datasource_type: 'Choose DataSource Type',
diff --git a/seatunnel-ui/src/locales/en_US/index.ts 
b/seatunnel-ui/src/locales/en_US/index.ts
index 6d57ce9d..6a25d3dc 100644
--- a/seatunnel-ui/src/locales/en_US/index.ts
+++ b/seatunnel-ui/src/locales/en_US/index.ts
@@ -25,6 +25,7 @@ import jobs from '@/locales/en_US/jobs'
 import tasks from '@/locales/en_US/tasks'
 import setting from '@/locales/en_US/setting'
 import datasource from '@/locales/en_US/datasource'
+import virtual_tables from '@/locales/en_US/virtual-tables'
 
 export default {
   login,
@@ -36,5 +37,6 @@ export default {
   jobs,
   tasks,
   setting,
-  datasource
+  datasource,
+  virtual_tables
 }
diff --git a/seatunnel-ui/src/locales/en_US/menu.ts 
b/seatunnel-ui/src/locales/en_US/menu.ts
index 800541f5..f3bd9587 100644
--- a/seatunnel-ui/src/locales/en_US/menu.ts
+++ b/seatunnel-ui/src/locales/en_US/menu.ts
@@ -23,5 +23,6 @@ export default {
   setting: 'Setting',
   logout: 'Logout',
   tasks: 'Tasks',
-  datasource: 'Datasource'
+  datasource: 'Datasource',
+  virtual_tables: 'Virtual Tables'
 }
diff --git a/seatunnel-ui/src/locales/en_US/virtual-tables.ts 
b/seatunnel-ui/src/locales/en_US/virtual-tables.ts
new file mode 100644
index 00000000..9b0650ce
--- /dev/null
+++ b/seatunnel-ui/src/locales/en_US/virtual-tables.ts
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export default {
+  virtual_tables: 'Virtual Tables',
+  create_virtual_tables: 'Create Virtual Tables',
+  edit_virtual_tables: 'Edit Virtual Tables',
+  source_type: 'Source Type',
+  source_type_tips: 'Please select a source type',
+  source_name: 'Source Name',
+  source_name_tips: 'Please enter source name',
+  table_name: 'Table Name',
+  database_name: 'Database Name',
+  creator: 'Creator',
+  creation_time: 'Creation Time',
+  updater: 'Updater',
+  update_time: 'Update Time',
+  operation: 'Operation',
+  edit: 'Edit',
+  delete: 'Delete',
+  confirm: 'Confirm',
+  delete_confirm: 'Delete?',
+  cancel: 'Cancel',
+  configure: 'Configure',
+  model: 'Model',
+  complete: 'Complete',
+  virtual_tables_name: 'Virtual Table Name',
+  virtual_tables_name_tips: 'Please enter a virtual table name',
+  next_step: 'Next Step',
+  previous_step: 'Previous Step',
+  table_structure: 'Table Structure',
+  add: 'Add a row',
+  field_name: 'Field Name',
+  field_name_tips: 'Please enter a field name',
+  field_type: 'Field Type',
+  is_null: 'Null',
+  is_primary_key: 'Primary Key',
+  description: 'Field Description',
+  yes: 'Yes',
+  no: 'No',
+  warning: 'Warning',
+  close_confirm_tips:
+    'This operation will lose the currently created virtual table',
+  save_data_tips: 'Please save the data in the table',
+  table_data_required_tips: 'Please add a record to the table',
+  default_value: 'Default Value',
+  create: 'Create',
+  search: 'Search'
+}
diff --git a/seatunnel-ui/src/locales/zh_CN/datasource.ts 
b/seatunnel-ui/src/locales/zh_CN/datasource.ts
index 6aaf46bd..f8321b5f 100644
--- a/seatunnel-ui/src/locales/zh_CN/datasource.ts
+++ b/seatunnel-ui/src/locales/zh_CN/datasource.ts
@@ -16,6 +16,7 @@
  */
 
 export default {
+  id: 'Id',
   datasource: '数据源',
   create_datasource: '创建源',
   choose_datasource_type: '选择源类型',
diff --git a/seatunnel-ui/src/locales/zh_CN/index.ts 
b/seatunnel-ui/src/locales/zh_CN/index.ts
index c3e11f4a..8ecf70c4 100644
--- a/seatunnel-ui/src/locales/zh_CN/index.ts
+++ b/seatunnel-ui/src/locales/zh_CN/index.ts
@@ -25,6 +25,7 @@ import jobs from '@/locales/zh_CN/jobs'
 import tasks from '@/locales/zh_CN/tasks'
 import setting from '@/locales/zh_CN/setting'
 import datasource from '@/locales/zh_CN/datasource'
+import virtual_tables from '@/locales/zh_CN/virtual-tables'
 
 export default {
   login,
@@ -36,5 +37,6 @@ export default {
   jobs,
   tasks,
   setting,
-  datasource
+  datasource,
+  virtual_tables
 }
diff --git a/seatunnel-ui/src/locales/zh_CN/menu.ts 
b/seatunnel-ui/src/locales/zh_CN/menu.ts
index 5270249f..1ab59deb 100644
--- a/seatunnel-ui/src/locales/zh_CN/menu.ts
+++ b/seatunnel-ui/src/locales/zh_CN/menu.ts
@@ -23,5 +23,6 @@ export default {
   setting: '设置',
   logout: '登出',
   tasks: '任务',
-  datasource: '数据源'
+  datasource: '数据源',
+  virtual_tables: '虚拟表'
 }
diff --git a/seatunnel-ui/src/locales/zh_CN/virtual-tables.ts 
b/seatunnel-ui/src/locales/zh_CN/virtual-tables.ts
new file mode 100644
index 00000000..f7adb43f
--- /dev/null
+++ b/seatunnel-ui/src/locales/zh_CN/virtual-tables.ts
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export default {
+  virtual_tables: '虚拟表',
+  create_virtual_tables: '创建虚拟表',
+  edit_virtual_tables: '编辑虚拟表',
+  source_type: '源类型',
+  source_type_tips: '请选择源类型',
+  source_name: '源名称',
+  source_name_tips: '请输入源名称',
+  table_name: '表名',
+  database_name: '库名',
+  creator: '创建人',
+  creation_time: '创建时间',
+  updater: '修改人',
+  update_time: '修改时间',
+  operation: '操作',
+  edit: '编辑',
+  delete: '删除',
+  confirm: '确定',
+  delete_confirm: '删除?',
+  cancel: '取消',
+  configure: '配置',
+  model: '模型',
+  complete: '完成',
+  virtual_tables_name: '虚拟表名',
+  virtual_tables_name_tips: '请输入虚拟表名',
+  next_step: '下一步',
+  previous_step: '上一步',
+  table_structure: '表结构',
+  add: '添加一行',
+  field_name: '字段名称',
+  field_name_tips: '请输入字段名称',
+  field_type: '字段类型',
+  is_null: '非空',
+  is_primary_key: '主键',
+  description: '字段描述',
+  yes: '是',
+  no: '否',
+  warning: '警告',
+  close_confirm_tips: '此操作会丢失当前创建的虚拟表',
+  save_data_tips: '请保存表格中的数据',
+  table_data_required_tips: '请在表结构中添加数据',
+  default_value: '默认值',
+  create: '创建',
+  search: '搜索'
+}
diff --git a/seatunnel-ui/src/router/routes.ts 
b/seatunnel-ui/src/router/routes.ts
index 15d9eeff..121ba1ec 100644
--- a/seatunnel-ui/src/router/routes.ts
+++ b/seatunnel-ui/src/router/routes.ts
@@ -21,6 +21,7 @@ import jobs from '@/router/jobs'
 import tasks from '@/router/tasks'
 import userManage from '@/router/user-manage'
 import datasource from '@/router/datasource'
+import virtualTables from '@/router/virtual-tables'
 import type { RouteRecordRaw } from 'vue-router'
 import type { Component } from 'vue'
 
@@ -36,7 +37,8 @@ const basePage: RouteRecordRaw[] = [
   jobs,
   tasks,
   userManage,
-  datasource
+  datasource,
+  virtualTables
 ]
 
 const loginPage: RouteRecordRaw[] = [
diff --git a/seatunnel-ui/src/locales/zh_CN/index.ts 
b/seatunnel-ui/src/router/virtual-tables.ts
similarity index 57%
copy from seatunnel-ui/src/locales/zh_CN/index.ts
copy to seatunnel-ui/src/router/virtual-tables.ts
index c3e11f4a..9224d206 100644
--- a/seatunnel-ui/src/locales/zh_CN/index.ts
+++ b/seatunnel-ui/src/router/virtual-tables.ts
@@ -15,26 +15,28 @@
  * limitations under the License.
  */
 
-import login from '@/locales/zh_CN/login'
-import menu from '@/locales/zh_CN/menu'
-import modal from '@/locales/zh_CN/modal'
-import user_manage from '@/locales/zh_CN/user-manage'
-import data_pipes from '@/locales/zh_CN/data-pipes'
-import log from '@/locales/zh_CN/log'
-import jobs from '@/locales/zh_CN/jobs'
-import tasks from '@/locales/zh_CN/tasks'
-import setting from '@/locales/zh_CN/setting'
-import datasource from '@/locales/zh_CN/datasource'
+import utils from '@/utils'
+import type { Component } from 'vue'
+
+const modules = import.meta.glob('/src/views/**/**.tsx')
+const components: { [key: string]: Component } = utils.mapping(modules)
 
 export default {
-  login,
-  menu,
-  modal,
-  user_manage,
-  data_pipes,
-  log,
-  jobs,
-  tasks,
-  setting,
-  datasource
+  path: '/virtual-tables',
+  name: 'virtual-tables',
+  meta: {
+    title: 'virtual-tables'
+  },
+  redirect: { name: 'virtual-tables-list' },
+  component: () => import('@/layouts/dashboard'),
+  children: [
+    {
+      path: '/virtual-tables/list',
+      name: 'virtual-tables-list',
+      component: components['virtual-tables-list'],
+      meta: {
+        title: 'virtual-tables-list'
+      }
+    }
+  ]
 }
diff --git a/seatunnel-ui/src/locales/en_US/menu.ts 
b/seatunnel-ui/src/service/virtual-tables/index.ts
similarity index 80%
copy from seatunnel-ui/src/locales/en_US/menu.ts
copy to seatunnel-ui/src/service/virtual-tables/index.ts
index 800541f5..1943eab2 100644
--- a/seatunnel-ui/src/locales/en_US/menu.ts
+++ b/seatunnel-ui/src/service/virtual-tables/index.ts
@@ -15,13 +15,12 @@
  * limitations under the License.
  */
 
-export default {
-  data_pipes: 'Data Pipes',
-  jobs: 'Jobs',
-  user_manage: 'User Manage',
-  help: 'Help',
-  setting: 'Setting',
-  logout: 'Logout',
-  tasks: 'Tasks',
-  datasource: 'Datasource'
+import { axios } from '@/service/service'
+
+export function virtualTableList(params: any): any {
+  return axios({
+    url: '/virtual_table/list',
+    method: 'get',
+    params
+  })
 }
diff --git a/seatunnel-ui/src/locales/en_US/menu.ts 
b/seatunnel-ui/src/service/virtual-tables/types.ts
similarity index 80%
copy from seatunnel-ui/src/locales/en_US/menu.ts
copy to seatunnel-ui/src/service/virtual-tables/types.ts
index 800541f5..3e7c6c26 100644
--- a/seatunnel-ui/src/locales/en_US/menu.ts
+++ b/seatunnel-ui/src/service/virtual-tables/types.ts
@@ -14,14 +14,3 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-export default {
-  data_pipes: 'Data Pipes',
-  jobs: 'Jobs',
-  user_manage: 'User Manage',
-  help: 'Help',
-  setting: 'Setting',
-  logout: 'Logout',
-  tasks: 'Tasks',
-  datasource: 'Datasource'
-}
diff --git a/seatunnel-ui/src/views/datasource/list/index.tsx 
b/seatunnel-ui/src/views/datasource/list/index.tsx
index 70232dd3..05275749 100644
--- a/seatunnel-ui/src/views/datasource/list/index.tsx
+++ b/seatunnel-ui/src/views/datasource/list/index.tsx
@@ -142,7 +142,7 @@ const DatasourceList = defineComponent({
             )
           }}
         </NCard>
-        <NCard title='' class={styles['mt-8']}>
+        <NCard>
           <NDataTable
             row-class-name='data-source-items'
             columns={columns}
diff --git a/seatunnel-ui/src/views/datasource/list/use-columns.ts 
b/seatunnel-ui/src/views/datasource/list/use-columns.ts
index a46df1c4..15bae636 100644
--- a/seatunnel-ui/src/views/datasource/list/use-columns.ts
+++ b/seatunnel-ui/src/views/datasource/list/use-columns.ts
@@ -19,11 +19,13 @@ import { h } from 'vue'
 import { useI18n } from 'vue-i18n'
 import { NPopover, NButton, NSpace } from 'naive-ui'
 import JsonHighlight from '../components/json-highlight'
+import { getTableColumn } from '@/common/table'
 
 export function useColumns(onCallback: Function) {
   const { t } = useI18n()
   const getColumns = () => {
     return [
+      ...getTableColumn([{ key: 'id', title: t('datasource.id') }]),
       {
         title: t('datasource.datasource_name'),
         key: 'datasourceName'
diff --git a/seatunnel-ui/src/views/virtual-tables/detail.tsx 
b/seatunnel-ui/src/views/virtual-tables/detail.tsx
new file mode 100644
index 00000000..7434d8dc
--- /dev/null
+++ b/seatunnel-ui/src/views/virtual-tables/detail.tsx
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { defineComponent } from 'vue'
+import {
+  NSpace,
+  NBreadcrumb,
+  NBreadcrumbItem,
+  NSteps,
+  NStep,
+  NButton,
+  NText,
+  NIcon,
+  NCard,
+  useDialog
+} from 'naive-ui'
+import StepOneForm from './StepOneForm'
+import StepTwoForm from './StepTwoForm'
+import StepTwoTable from './StepTwoTable'
+import StepThreeParams from './StepThreeParams'
+import { PlusOutlined } from '@vicons/antd'
+import { useRoute, useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
+import { useDetail } from './use-detail'
+import styles from './index.module.scss'
+
+const VirtualTablesDetail = defineComponent({
+  name: 'VirtualTablesDetail',
+  setup() {
+    const { t } = useI18n()
+    const route = useRoute()
+    const router = useRouter()
+    const dialog = useDialog()
+    const {
+      state,
+      stepOneFormRef,
+      stepTwoFormRef,
+      onAddRecord,
+      onChangeStep,
+      createOrUpdate
+    } = useDetail(route.params.id as string)
+
+    const onClose = () => {
+      dialog.warning({
+        title: t('virtual_tables.warning'),
+        content: t('virtual_tables.close_confirm_tips'),
+        onPositiveClick: () => {
+          router.push({
+            name: 'datasource-list',
+            query: { tab: 'virtual-tables' }
+          })
+        },
+        positiveText: t('virtual_tables.confirm'),
+        negativeText: t('virtual_tables.cancel')
+      })
+    }
+
+    return () => (
+      <NSpace vertical>
+        <NBreadcrumb>
+          <NBreadcrumbItem
+            // @ts-ignore
+            onClick={onClose}
+          >
+            {t('virtual_tables.virtual_tables')}
+          </NBreadcrumbItem>
+          <NBreadcrumbItem>
+            {t(
+              route.params.id
+                ? 'virtualTables.edit_virtual_tables'
+                : 'virtualTables.create_virtual_tables'
+            )}
+          </NBreadcrumbItem>
+        </NBreadcrumb>
+        <NCard
+          title={t(
+            route.params.id
+              ? 'virtualTables.edit_virtual_tables'
+              : 'virtualTables.create_virtual_tables'
+          )}
+        >
+          <div class={styles['detail-content']}>
+            <NSteps current={state.current} class={styles['detail-step']}>
+              <NStep title={t('virtual_tables.configure')} />
+              <NStep title={t('virtual_tables.model')} />
+              <NStep title={t('virtual_tables.complete')} />
+            </NSteps>
+            <div class={styles['width-100']} v-show={state.current === 1}>
+              <NSpace justify='center'>
+                <StepOneForm params={state.stepOne} ref={stepOneFormRef} />
+              </NSpace>
+            </div>
+            <div class={styles['detail-step-two']} v-show={state.current === 
2}>
+              <StepTwoForm ref={stepTwoFormRef} />
+              <div class={styles['detail-table-header']}>
+                <NText class={styles['detail-table-title']}>
+                  {t('virtual_tables.table_structure')}
+                </NText>
+                <NButton text type='primary' onClick={onAddRecord}>
+                  {{
+                    icon: () => (
+                      <NIcon>
+                        <PlusOutlined />
+                      </NIcon>
+                    ),
+                    default: () => t('virtual_tables.add')
+                  }}
+                </NButton>
+              </div>
+              <StepTwoTable
+                //list={state.stepTwo.list}
+                fieldTypes={state.fieldTypes}
+              />
+            </div>
+            <div
+              class={styles['detail-step-three']}
+              v-show={state.current === 3}
+            >
+              <div class={styles['detail-step-three-params']}>
+                <StepThreeParams
+                  class={styles['detail-step-three-left']}
+                  params={[
+                    {
+                      label: t('virtual_tables.source_type'),
+                      value: state.stepOne.pluginName || ''
+                    },
+                    {
+                      label: t('virtual_tables.source_name'),
+                      value: state.stepOne.datasourceName || ''
+                    },
+                    {
+                      label: t('virtual_tables.virtual_tables_name'),
+                      value: state.stepOne.tableName || ''
+                    }
+                  ]}
+                />
+                <StepThreeParams
+                  class={styles['detail-step-three-right']}
+                  params={state.stepTwo.config}
+                  cols={3}
+                />
+              </div>
+              <StepTwoTable
+                class={styles['width-100']}
+                //list={state.stepTwo.list}
+                plain
+                fieldTypes={state.fieldTypes}
+              />
+            </div>
+          </div>
+          <NSpace justify='end'>
+            <NButton
+              v-show={state.current !== 1}
+              type='primary'
+              onClick={() => void onChangeStep(-1)}
+            >
+              {t('virtual_tables.previous_step')}
+            </NButton>
+            <NButton onClick={onClose}>{t('virtual_tables.cancel')}</NButton>
+
+            {state.current !== 3 && (
+              <NButton
+                type='primary'
+                onClick={() => void onChangeStep(1)}
+                loading={state.goNexting}
+              >
+                {t('virtual_tables.next_step')}
+              </NButton>
+            )}
+            <NButton
+              v-show={state.current === 3}
+              onClick={createOrUpdate}
+              loading={state.saving}
+              type='primary'
+            >
+              {t('virtual_tables.confirm')}
+            </NButton>
+          </NSpace>
+        </NCard>
+      </NSpace>
+    )
+  }
+})
+
+export default VirtualTablesDetail
diff --git a/seatunnel-ui/src/views/virtual-tables/index.module.scss 
b/seatunnel-ui/src/views/virtual-tables/index.module.scss
new file mode 100644
index 00000000..4139b9d7
--- /dev/null
+++ b/seatunnel-ui/src/views/virtual-tables/index.module.scss
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.type-width {
+  width: 220px;
+}
+.width-100 {
+  width: 100%;
+}
+.detail-step {
+  width: 50vw;
+  margin-top: 20px;
+  margin-left: 16vw;
+  margin-bottom: 30px;
+}
+.detail-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.detail-step-two {
+  width: 100%;
+  padding-bottom: 20px;
+  padding-top: 10px;
+}
+.detail-step-three {
+  width: 100%;
+  padding-bottom: 20px;
+}
+.detail-table-header {
+  height: 30px;
+  display: flex;
+  justify-content: space-between;
+}
+.detail-table-title {
+  font-weight: bold;
+}
+.step-two-table {
+  :global {
+    .n-form-item-feedback-wrapper {
+      line-height: 14px;
+      min-height: 14px;
+    }
+  }
+}
+.table-cell-center {
+  text-align: center !important;
+}
+.edit-row {
+  :global {
+    td {
+      padding-bottom: 0px;
+    }
+  }
+}
+.detail-step-three-params {
+  display: flex;
+  margin-bottom: 10px;
+  justify-content: space-between;
+}
+.detail-step-three-left {
+  width: 33%;
+  margin-right: 10px;
+}
+.detail-step-three-right {
+  flex-grow: 1;
+}
diff --git a/seatunnel-ui/src/views/virtual-tables/list/index.tsx 
b/seatunnel-ui/src/views/virtual-tables/list/index.tsx
new file mode 100644
index 00000000..03bfdea1
--- /dev/null
+++ b/seatunnel-ui/src/views/virtual-tables/list/index.tsx
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { defineComponent } from 'vue'
+import {
+  NButton,
+  NInput,
+  NSelect,
+  NIcon,
+  NSpace,
+  NDataTable,
+  NPagination,
+  NCard,
+  SelectOption,
+  SelectGroupOption
+} from 'naive-ui'
+import { useI18n } from 'vue-i18n'
+import { useRouter } from 'vue-router'
+import { useTable } from './use-table'
+import { useColumns } from './use-columns'
+//import { useSource } from '../datasource/list/use-source'
+import styles from '../index.module.scss'
+
+const VirtualTablesList = defineComponent({
+  setup() {
+    const { t } = useI18n()
+    const router = useRouter()
+    //const { state: sourceState } = useSource(true)
+    const { columns } = useColumns(
+      (id: string, type: 'edit' | 'delete') => {
+        if (type === 'edit') {
+          router.push({ name: 'virtual-tables-editor', params: { id: id } })
+        } else {
+          onDelete(id)
+        }
+      }
+    )
+    const {
+      state,
+      onSearch,
+      onDelete,
+      onPageChange,
+      onPageSizeChange
+    } = useTable()
+
+    return () => (
+      <NSpace vertical>
+        <NCard title={t('virtual_tables.virtual_tables')}>
+          {{
+            'header-extra': () => <NSpace>
+              <NSelect
+                v-model:value={state.params.pluginName}
+                clearable
+                placeholder={t('virtual_tables.source_type_tips')}
+                //options={
+                //  sourceState.types as Array<SelectGroupOption | 
SelectOption>
+                //}
+                class={styles['type-width']}
+              />
+              <NInput
+                v-model:value={state.params.datasourceName}
+                clearable
+                placeholder={t('virtual_tables.source_name_tips')}
+              />
+              <NButton type='primary' onClick={onSearch}>
+                {t('virtual_tables.search')}
+              </NButton>
+              <NButton
+                onClick={() => {
+                  router.push({ name: 'virtual-tables-create' })
+                }}
+                type='success'
+              >
+                {t('virtual_tables.create')}
+              </NButton>
+            </NSpace>
+          }}
+        </NCard>
+        <NCard>
+          <NSpace vertical>
+            <NDataTable
+              columns={columns.value}
+              data={state.list}
+              loading={state.loading}
+              striped
+            />
+            <NSpace justify='center'>
+              <NPagination
+                v-model:page={state.page}
+                v-model:page-size={state.pageSize}
+                item-count={state.itemCount}
+                show-size-picker
+                page-sizes={[10, 30, 50]}
+                show-quick-jumper
+                on-update:page={onPageChange}
+                on-update:page-size={onPageSizeChange}
+              />
+            </NSpace>
+          </NSpace>
+        </NCard>
+      </NSpace>
+    )
+  }
+})
+
+export default VirtualTablesList
diff --git a/seatunnel-ui/src/views/virtual-tables/list/use-columns.ts 
b/seatunnel-ui/src/views/virtual-tables/list/use-columns.ts
new file mode 100644
index 00000000..860dd000
--- /dev/null
+++ b/seatunnel-ui/src/views/virtual-tables/list/use-columns.ts
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { h, ref, watch, onMounted } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { EditOutlined } from '@vicons/antd'
+import { NButton, NSpace } from 'naive-ui'
+//import type { TableColumns, VirtualTableRecord } from '../types'
+
+export function useColumns(onCallback: Function) {
+  const { t } = useI18n()
+  const columns = ref()
+  const getColumns = () => {
+    const columns = [
+      {
+        title: 'ID',
+        key: 'id',
+        render: (ignore: any, index: number) => index + 1
+      },
+      {
+        title: t('virtual_tables.table_name'),
+        key: 'tableName',
+        align: 'left'
+      },
+      {
+        title: t('virtual_tables.database_name'),
+        key: 'databaseName',
+        align: 'left'
+      },
+      {
+        title: t('virtual_tables.source_name'),
+        key: 'datasourceName',
+        align: 'left'
+      },
+      {
+        title: t('virtual_tables.source_type'),
+        key: 'pluginName'
+      },
+      {
+        title: t('virtual_tables.creator'),
+        key: 'createUserName'
+      },
+      {
+        title: t('virtual_tables.creation_time'),
+        key: 'createTime'
+        //render: (rowData: VirtualTableRecord) =>
+        //  renderTableTime(rowData.createTime)
+      },
+      {
+        title: t('virtual_tables.updater'),
+        key: 'updateUserName'
+      },
+      {
+        title: t('virtual_tables.update_time'),
+        key: 'updateTime'
+        //render: (rowData: VirtualTableRecord) =>
+        //  renderTableTime(rowData.createTime)
+      },
+      {
+        title: t('virtual_tables.operation'),
+        key: 'operation',
+        render: (row: any) =>
+          h(NSpace, null, {
+            default: () => [
+              h(
+                NButton,
+                {
+                  text: true,
+                  onClick: () => void onCallback(row.tableId, 'edit')
+                },
+                {
+                  default: () => t('virtual_tables.edit')
+                }
+              ),
+              h(
+                NButton,
+                {
+                  text: true,
+                  onClick: () => void onCallback(row.tableId, 'delete')
+                },
+                { default: () => t('virtual_tables.delete') }
+              )
+            ]
+          })
+      }
+    ]
+    return columns
+  }
+
+  watch(useI18n().locale, () => {
+    columns.value = getColumns()
+  })
+
+  onMounted(() => {
+    columns.value = getColumns()
+  })
+
+  return {
+    columns
+  }
+}
diff --git a/seatunnel-ui/src/views/virtual-tables/list/use-table.ts 
b/seatunnel-ui/src/views/virtual-tables/list/use-table.ts
new file mode 100644
index 00000000..a75d9294
--- /dev/null
+++ b/seatunnel-ui/src/views/virtual-tables/list/use-table.ts
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { onMounted, reactive } from 'vue'
+import {
+  virtualTableList
+  //deleteVirtualTable
+} from '@/service/virtual-tables'
+import { useRoute, useRouter } from 'vue-router'
+//import type { Params } from '../types'
+
+export function useTable() {
+  const initialParams: any = {
+    pluginName: null,
+    datasourceName: null
+  }
+  const state = reactive({
+    params: { ...initialParams },
+    list: [],
+    loading: false,
+    page: 1,
+    pageSize: 10,
+    itemCount: 0
+  })
+  const route = useRoute()
+  const router = useRouter()
+
+  const getList = async () => {
+    const result = await virtualTableList({
+      pageNo: state.page,
+      pageSize: state.pageSize,
+      ...state.params
+    })
+    console.log(result)
+    state.list = result?.data
+    state.itemCount = result?.total
+  }
+
+  const updateList = () => {
+    if (state.list.length === 1 && state.page > 1) {
+      --state.page
+    }
+    getList()
+  }
+
+  const onDelete = async (id: string) => {
+    //await deleteVirtualTable(id)
+    updateList()
+  }
+
+  const initSearch = () => {
+    const { pluginName, datasourceName } = route.query
+    if (pluginName) {
+      state.params.pluginName = pluginName as string
+      if (datasourceName) {
+        state.params.datasourceName = datasourceName as string
+      }
+    }
+  }
+
+  const onSearch = () => {
+    const query = (
+      state.params.pluginName ? { pluginName: state.params.pluginName } : null
+    ) as any
+
+    if (state.params.datasourceName) {
+      query.datasourceName = state.params.datasourceName
+    }
+
+    if (query) {
+      router.replace({ query: { tab: route.query.tab, ...query } })
+    }
+
+    state.page = 1
+    getList()
+  }
+
+  const onPageChange = (page: number) => {
+    state.page = page
+    getList()
+  }
+
+  const onPageSizeChange = (pageSize: number) => {
+    state.page = 1
+    state.pageSize = pageSize
+    getList()
+  }
+
+  onMounted(() => {
+    initSearch()
+    onSearch()
+  })
+
+  return {
+    state,
+    onSearch,
+    onDelete,
+    onPageChange,
+    onPageSizeChange
+  }
+}
diff --git a/seatunnel-ui/src/views/virtual-tables/step-one-form.tsx 
b/seatunnel-ui/src/views/virtual-tables/step-one-form.tsx
new file mode 100644
index 00000000..632a32d5
--- /dev/null
+++ b/seatunnel-ui/src/views/virtual-tables/step-one-form.tsx
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { defineComponent, ref, toRef } from 'vue'
+import {
+  NForm,
+  NFormItem,
+  NSelect,
+  NInput,
+  SelectOption,
+  SelectGroupOption
+} from 'naive-ui'
+import { useI18n } from 'vue-i18n'
+//import { useSource } from '../datasource/list/use-source'
+import { useTable } from '../datasource/list/use-table'
+import styles from './index.module.scss'
+
+const StepOneForm = defineComponent({
+  name: 'StepOneForm',
+  props: {
+    params: {
+      type: Object,
+      default: {}
+    }
+  },
+  setup(props, { expose }) {
+    const { t } = useI18n()
+    //const { state: sourceState } = useSource(true)
+    const { data: datasourceState, getList } = useTable()
+    const rules = {
+      pluginName: {
+        required: true,
+        trigger: ['input', 'blur'],
+        message: t('virtual_tables.source_type_tips')
+      },
+      datasourceName: {
+        required: true,
+        trigger: ['input', 'blur'],
+        message: t('virtual_tables.source_name_tips')
+      },
+      tableName: {
+        required: true,
+        trigger: ['input', 'blur'],
+        message: t('virtual_tables.virtual_tables_name_tips')
+      }
+    }
+    const paramsRef = toRef(props, 'params')
+    const stepOneFormRef = ref()
+
+    const handleUpdateType = (type: string) => {
+      if (type === paramsRef.value.pluginName) return
+      datasourceState.pageSize = 99999
+      //getList(type)
+      paramsRef.value.datasourceName = null
+      paramsRef.value.tableName = null
+    }
+
+    expose({
+      validate: async () => {
+        await stepOneFormRef.value.validate()
+      }
+    })
+
+    return () => (
+      <NForm
+        rules={rules}
+        ref={stepOneFormRef}
+        require-mark-placement='left'
+        label-align='right'
+        labelPlacement='left'
+        model={paramsRef.value}
+        labelWidth={100}
+      >
+        <NFormItem
+          label={t('virtual_tables.source_type')}
+          path='pluginName'
+          show-require-mark
+        >
+          <NSelect
+            v-model:value={props.params.pluginName}
+            filterable
+            placeholder={t('virtual_tables.source_type_tips')}
+            //options={
+            //  sourceState.types as Array<SelectGroupOption | SelectOption>
+            //}
+            //loading={sourceState.loading}
+            class={styles['type-width']}
+            onUpdateValue={(value) => void handleUpdateType(value)}
+          />
+        </NFormItem>
+        <NFormItem
+          label={t('virtual_tables.source_name')}
+          path='datasourceName'
+          show-require-mark
+        >
+          <NSelect
+            v-model:value={paramsRef.value.datasourceName}
+            filterable
+            placeholder={t('virtual_tables.source_name_tips')}
+            options={datasourceState.list.map(
+              (item: { datasourceName: string; id: string }) => ({
+                label: item.datasourceName,
+                value: item.datasourceName,
+                id: item.id
+              })
+            )}
+            //loading={datasourceState.loading}
+            class={styles['type-width']}
+            onUpdateValue={(value, option: { id: string }) => {
+              paramsRef.value.datasourceId = option.id
+            }}
+          />
+        </NFormItem>
+        <NFormItem
+          label={t('virtual_tables.virtual_tables_name')}
+          path='tableName'
+          show-require-mark
+        >
+          <NInput
+            v-model:value={paramsRef.value.tableName}
+            clearable
+            placeholder={t('virtual_tables.virtual_tables_name_tips')}
+            class={styles['type-width']}
+          />
+        </NFormItem>
+      </NForm>
+    )
+  }
+})
+
+export default StepOneForm
diff --git a/seatunnel-ui/src/locales/zh_CN/index.ts 
b/seatunnel-ui/src/views/virtual-tables/step-three-params.tsx
similarity index 52%
copy from seatunnel-ui/src/locales/zh_CN/index.ts
copy to seatunnel-ui/src/views/virtual-tables/step-three-params.tsx
index c3e11f4a..92ee2b9c 100644
--- a/seatunnel-ui/src/locales/zh_CN/index.ts
+++ b/seatunnel-ui/src/views/virtual-tables/step-three-params.tsx
@@ -15,26 +15,37 @@
  * limitations under the License.
  */
 
-import login from '@/locales/zh_CN/login'
-import menu from '@/locales/zh_CN/menu'
-import modal from '@/locales/zh_CN/modal'
-import user_manage from '@/locales/zh_CN/user-manage'
-import data_pipes from '@/locales/zh_CN/data-pipes'
-import log from '@/locales/zh_CN/log'
-import jobs from '@/locales/zh_CN/jobs'
-import tasks from '@/locales/zh_CN/tasks'
-import setting from '@/locales/zh_CN/setting'
-import datasource from '@/locales/zh_CN/datasource'
+import { defineComponent, PropType } from 'vue'
+import { NCard, NGrid, NGi, NSpace } from 'naive-ui'
 
-export default {
-  login,
-  menu,
-  modal,
-  user_manage,
-  data_pipes,
-  log,
-  jobs,
-  tasks,
-  setting,
-  datasource
-}
+const StepThreeParams = defineComponent({
+  name: 'StepThreeParams',
+  props: {
+    params: {
+      type: Array as PropType<{ label: string; value: string }[]>,
+      default: []
+    },
+    cols: {
+      type: Number,
+      default: 1
+    }
+  },
+  setup(props) {
+    return () => (
+      <NCard>
+        <NGrid cols={props.cols}>
+          {props.params.map((item) => (
+            <NGi>
+              <NSpace>
+                <span>{item.label}:</span>
+                <span>{item.value}</span>
+              </NSpace>
+            </NGi>
+          ))}
+        </NGrid>
+      </NCard>
+    )
+  }
+})
+
+export default StepThreeParams
diff --git a/seatunnel-ui/src/views/virtual-tables/step-two-form.tsx 
b/seatunnel-ui/src/views/virtual-tables/step-two-form.tsx
new file mode 100644
index 00000000..a14793dd
--- /dev/null
+++ b/seatunnel-ui/src/views/virtual-tables/step-two-form.tsx
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { defineComponent, reactive, ref } from 'vue'
+import { NForm } from 'naive-ui'
+import { useI18n } from 'vue-i18n'
+import { DynamicFormItem } from '@/components/dynamic-form/dynamic-form-item'
+//import { StructureItem } from '@/store/datasource/form-structures'
+//import { getDynamicConfig } from '@/service/modules/virtual-table'
+import { useFormField } from '@/components/dynamic-form/use-form-field'
+import { useFormRequest } from '@/components/dynamic-form/use-form-request'
+import { useFormValidate } from '@/components/dynamic-form/use-form-validate'
+import { useFormStructure } from '@/components/dynamic-form/use-form-structure'
+
+const StepTwoForm = defineComponent({
+  name: 'StepTwoForm',
+  setup(props, { expose }) {
+    const { t } = useI18n()
+    const stepTwoFormRef = ref()
+
+    const state = reactive({
+      rules: {},
+      //formStructure: [] as StructureItem[],
+      locales: {} as any,
+      formName: 'step-two-form',
+      detailForm: {} as { [key: string]: string },
+      config: [] as { key: string; value: string; label: string }[]
+    })
+
+    const getFormItems = async (pluginName: string, datasourceName: string) => 
{
+      if (!pluginName || !datasourceName) return false
+      //const result = await getDynamicConfig({
+      //  pluginName,
+      //  datasourceName
+      //})
+      try {
+        //const res = JSON.parse(result)
+        //
+        //state.locales = res.locales
+        //Object.assign(state.detailForm, useFormField(res.forms))
+        //Object.assign(
+        //  state.rules,
+        //  useFormValidate(res.forms, state.detailForm, t)
+        //)
+        //state.formStructure = useFormStructure(
+        //  res.apis ? useFormRequest(res.apis, res.forms) : res.forms
+        //) as any
+        //
+        //state.formStructure = res.forms.map((item: any) => ({
+        //  ...item,
+        //  span: 8
+        //}))
+        return true
+      } catch (err) {
+        return false
+      }
+    }
+
+    const getValues = async () => {
+      await stepTwoFormRef.value.validate()
+      //return state.formStructure.map((item) => {
+      //  return {
+      //    label: item.label,
+      //    key: item.field,
+      //    value: state.detailForm[item.field]
+      //  }
+      //})
+    }
+
+    const setValues = (values: { [key: string]: string }) => {
+      Object.assign(state.detailForm, values)
+    }
+
+    expose({
+      validate: async () => {
+        await stepTwoFormRef.value.validate()
+      },
+      getFormItems,
+      getValues,
+      setValues
+    })
+
+    return () => (
+      <NForm
+        rules={state.rules}
+        ref={stepTwoFormRef}
+        require-mark-placement='left'
+        model={state.detailForm}
+        labelWidth={100}
+      >
+        {/*{state.formStructure.length > 0 && (*/}
+        {/*  <DynamicFormItem*/}
+        {/*    model={state.detailForm}*/}
+        {/*    formStructure={state.formStructure}*/}
+        {/*    name={state.formName}*/}
+        {/*    locales={state.locales}*/}
+        {/*  />*/}
+        {/*)}*/}
+      </NForm>
+    )
+  }
+})
+
+export default StepTwoForm
diff --git a/seatunnel-ui/src/views/virtual-tables/step-two-table.tsx 
b/seatunnel-ui/src/views/virtual-tables/step-two-table.tsx
new file mode 100644
index 00000000..c90e85e3
--- /dev/null
+++ b/seatunnel-ui/src/views/virtual-tables/step-two-table.tsx
@@ -0,0 +1,327 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { defineComponent, PropType, toRef, ref, onMounted } from 'vue'
+import {
+  NTable,
+  NText,
+  NTooltip,
+  NFormItem,
+  NInput,
+  NSelect,
+  NSpace,
+  NButton,
+  NPopconfirm,
+  NIcon,
+  NEmpty
+} from 'naive-ui'
+import { useI18n } from 'vue-i18n'
+import { EditOutlined, DeleteOutlined } from '@vicons/antd'
+import styles from './index.module.scss'
+//import type { IDetailTableRecord } from './types'
+
+const EditRow = defineComponent({
+  name: 'EditRow',
+  props: {
+    row: {
+      type: Object as PropType<any>,
+      default: {}
+    },
+    plain: {
+      type: Boolean,
+      default: false
+    },
+    fieldTypes: {
+      type: Array as PropType<string[]>,
+      default: []
+    }
+  },
+  emits: ['updateValue', 'delete'],
+  setup(props, { emit }) {
+    const { t } = useI18n()
+    const inputRef = ref()
+    const fieldTypeRef = ref()
+    const onUpdateValue = (field: keyof any, value: any) => {
+      emit('updateValue', field, value)
+    }
+    onMounted(() => {
+      inputRef.value.focus()
+    })
+    return () => (
+      <tr>
+        <td>
+          <NFormItem
+            showLabel={false}
+            feedback={
+              props.row.fieldName ? '' : t('virtual_tables.field_name_tips')
+            }
+            validation-status={props.row.fieldName ? '' : 'error'}
+          >
+            <NInput
+              autofocus
+              value={props.row.fieldName}
+              clearable
+              onUpdateValue={(value) => void onUpdateValue('fieldName', value)}
+              ref={inputRef}
+            />
+          </NFormItem>
+        </td>
+        <td>
+          <NFormItem showLabel={false} v-show={props.row.isEdit}>
+            <NSelect
+              ref={fieldTypeRef}
+              value={props.row.fieldType}
+              options={props.fieldTypes.map((item) => ({
+                label: item,
+                value: item
+              }))}
+              filterable
+              onUpdateValue={(value) => void onUpdateValue('fieldType', value)}
+            />
+          </NFormItem>
+        </td>
+        <td>
+          <NFormItem showLabel={false}>
+            <NSelect
+              value={props.row.nullable as number}
+              options={[
+                { value: 1, label: t('virtual_tables.yes') },
+                { value: 0, label: t('virtual_tables.no') }
+              ]}
+              onUpdateValue={(value) => void onUpdateValue('nullable', value)}
+            />
+          </NFormItem>
+        </td>
+        <td>
+          <NFormItem showLabel={false}>
+            <NSelect
+              value={props.row.primaryKey as number}
+              options={[
+                { value: 1, label: t('virtual_tables.yes') },
+                { value: 0, label: t('virtual_tables.no') }
+              ]}
+              onUpdateValue={(value) => void onUpdateValue('primaryKey', 
value)}
+            />
+          </NFormItem>
+        </td>
+        <td>
+          <NFormItem showLabel={false}>
+            <NInput
+              value={props.row.fieldComment}
+              clearable
+              onUpdateValue={(value) =>
+                void onUpdateValue('fieldComment', value)
+              }
+            />
+          </NFormItem>
+        </td>
+        <td>
+          <NFormItem showLabel={false}>
+            <NInput
+              value={props.row.defaultValue}
+              clearable
+              onUpdateValue={(value) =>
+                void onUpdateValue('defaultValue', value)
+              }
+            />
+          </NFormItem>
+        </td>
+        {!props.plain && (
+          <td>
+            <NFormItem showLabel={false}>
+              <NSpace align='start'>
+                <NButton
+                  size='small'
+                  onClick={() => void onUpdateValue('isEdit', false)}
+                >
+                  {t('virtual_tables.cancel')}
+                </NButton>
+                <NButton
+                  type='primary'
+                  size='small'
+                  onClick={() => {
+                    if (!props.row.fieldName) {
+                      inputRef.value.focus()
+                      return
+                    }
+                    if (!props.row.fieldType) {
+                      fieldTypeRef.value.focus()
+                      return
+                    }
+                    onUpdateValue('isEdit', false)
+                  }}
+                >
+                  {t('virtual_tables.confirm')}
+                </NButton>
+              </NSpace>
+            </NFormItem>
+          </td>
+        )}
+      </tr>
+    )
+  }
+})
+
+const StepTwoTable = defineComponent({
+  name: 'StepTwoTable',
+  props: {
+    list: {
+      type: Array as PropType<any[]>,
+      default: []
+    },
+    plain: {
+      type: Boolean,
+      default: false
+    },
+    fieldTypes: {
+      type: Array as PropType<string[]>,
+      default: []
+    }
+  },
+  setup(props) {
+    const { t } = useI18n()
+    const listRef = toRef(props, 'list')
+    return () => (
+      <NTable striped class={styles['step-two-table']}>
+        <thead>
+          <th>
+            <NText type='error'>*</NText>
+            {t('virtual_tables.field_name')}
+          </th>
+          <th class={styles['table-cell-center']}>
+            <NText type='error'>*</NText>
+            {t('virtual_tables.field_type')}
+          </th>
+          <th class={styles['table-cell-center']}>
+            <NText type='error'>*</NText>
+            {t('virtual_tables.is_null')}
+          </th>
+          <th class={styles['table-cell-center']}>
+            <NText type='error'>*</NText>
+            {t('virtual_tables.is_primary_key')}
+          </th>
+          <th>{t('virtual_tables.description')}</th>
+          <th>{t('virtual_tables.default_value')}</th>
+          {!props.plain && <th>{t('virtual_tables.operation')}</th>}
+        </thead>
+        <tbody>
+          {listRef.value.map((row, index) =>
+            row.isEdit && !props.plain ? (
+              <EditRow
+                key={row.key}
+                row={row}
+                plain={props.plain}
+                fieldTypes={props.fieldTypes}
+                onUpdateValue={(
+                  field: keyof any,
+                  value: any
+                ) => {
+                  // @ts-ignore
+                  listRef.value[index][field] = value
+                }}
+                class={styles[row.isEdit ? 'edit-row' : 'plain-row']}
+              />
+            ) : (
+              <tr>
+                <td>
+                  <span>{row.fieldName}</span>
+                </td>
+                <td class={styles['table-cell-center']}>
+                  <span>{row.fieldType}</span>
+                </td>
+                <td class={styles['table-cell-center']}>
+                  <span>
+                    {row.nullable
+                      ? t('virtual_tables.yes')
+                      : t('virtual_tables.no')}
+                  </span>
+                </td>
+                <td class={styles['table-cell-center']}>
+                  <span>
+                    {row.primaryKey
+                      ? t('virtual_tables.yes')
+                      : t('virtual_tables.no')}
+                  </span>
+                </td>
+                <td>
+                  <span>{row.fieldComment}</span>
+                </td>
+                <td>
+                  <span>{row.defaultValue}</span>
+                </td>
+                {!props.plain && (
+                  <td>
+                    <NSpace align='start'>
+                      <NTooltip>
+                        {{
+                          trigger: () => (
+                            <NButton
+                              type='primary'
+                              circle
+                              size='small'
+                              onClick={() => {
+                                listRef.value[index]['isEdit'] = true
+                              }}
+                            >
+                              <NIcon>
+                                <EditOutlined />
+                              </NIcon>
+                            </NButton>
+                          ),
+                          default: () => t('virtual_tables.edit')
+                        }}
+                      </NTooltip>
+                      <NTooltip>
+                        {{
+                          trigger: () => (
+                            <NPopconfirm
+                              onPositiveClick={() => {
+                                listRef.value.splice(index, 1)
+                              }}
+                            >
+                              {{
+                                trigger: () => (
+                                  <NButton type='error' circle size='small'>
+                                    <NIcon>
+                                      <DeleteOutlined />
+                                    </NIcon>
+                                  </NButton>
+                                ),
+                                default: () => 
t('virtual_tables.delete_confirm')
+                              }}
+                            </NPopconfirm>
+                          ),
+                          default: () => t('virtual_tables.delete')
+                        }}
+                      </NTooltip>
+                    </NSpace>
+                  </td>
+                )}
+              </tr>
+            )
+          )}
+          <tr v-show={!listRef.value.length}>
+            <td colspan={6}>
+              <NEmpty />
+            </td>
+          </tr>
+        </tbody>
+      </NTable>
+    )
+  }
+})
+
+export default StepTwoTable
diff --git a/seatunnel-ui/src/locales/zh_CN/menu.ts 
b/seatunnel-ui/src/views/virtual-tables/types.ts
similarity index 65%
copy from seatunnel-ui/src/locales/zh_CN/menu.ts
copy to seatunnel-ui/src/views/virtual-tables/types.ts
index 5270249f..09cee8c8 100644
--- a/seatunnel-ui/src/locales/zh_CN/menu.ts
+++ b/seatunnel-ui/src/views/virtual-tables/types.ts
@@ -14,14 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+export type { TableColumns } from 'naive-ui/es/data-table/src/interface'
+export type { DataTableColumns } from 'naive-ui'
+//export type {
+//  VirtualTableDetail,
+//  VirtualTableRecord,
+//  IDetailTableRecord
+//} from '@/service/modules/virtual-table/types'
+//import type { VirtualTableListParameters } from 
'@/service/modules/virtual-table/types'
 
-export default {
-  data_pipes: '数据管道',
-  jobs: '工作',
-  user_manage: '用户管理',
-  help: '帮助',
-  setting: '设置',
-  logout: '登出',
-  tasks: '任务',
-  datasource: '数据源'
-}
+//export type Params = Omit<VirtualTableListParameters, 'pageNo' | 'pageSize'>
diff --git a/seatunnel-ui/src/views/virtual-tables/use-detail.ts 
b/seatunnel-ui/src/views/virtual-tables/use-detail.ts
new file mode 100644
index 00000000..0c2f709b
--- /dev/null
+++ b/seatunnel-ui/src/views/virtual-tables/use-detail.ts
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { reactive, ref, onMounted } from 'vue'
+import { useI18n } from 'vue-i18n'
+//import {
+//  getVirtualTableDetail,
+//  createVirtualTable,
+//  updateVirtualTable,
+//  getFieldType
+//} from '@/service/modules/virtual-table'
+import { omit } from 'lodash'
+import { useRouter } from 'vue-router'
+//import type { IDetailTableRecord, VirtualTableDetail } from './types'
+
+export const useDetail = (id: string) => {
+  const state = reactive({
+    current: 1,
+    loading: false,
+    saving: false,
+    stepOne: {
+      pluginName: null,
+      datasourceName: null,
+      tableName: '',
+      datasourceId: ''
+    },
+    stepTwo: {
+      //list: [] as IDetailTableRecord[],
+      loading: false,
+      config: []
+    },
+    fieldTypes: [] as string[],
+    goNexting: false
+  })
+  const { t } = useI18n()
+  let tempDatabaseProperties: any
+  const router = useRouter()
+  const defaultRecord = {
+    fieldName: '',
+    fieldType: '',
+    nullable: 0,
+    primaryKey: 0,
+    isEdit: true
+  }
+  const stepOneFormRef = ref()
+  const stepTwoFormRef = ref()
+
+  const queryById = async () => {
+    if (state.loading) return {}
+    state.loading = true
+    //const res = await getVirtualTableDetail(id)
+    //state.stepOne.pluginName = res.pluginName
+    //state.stepOne.datasourceName = res.datasourceName
+    //state.stepOne.datasourceId = res.datasourceId
+    //state.stepOne.tableName = res.tableName
+    //state.stepTwo.list = res.fields.map((item: { [key: string]: any }) => ({
+    //  ...item,
+    //  nullable: Number(item.nullable),
+    //  primaryKey: Number(item.primaryKey)
+    //}))
+    //tempDatabaseProperties = res.datasourceProperties
+    state.loading = false
+  }
+
+  const queryFieldsType = async () => {
+    //const res = await getFieldType()
+    //state.fieldTypes = res
+    //defaultRecord.fieldType = state.fieldTypes[0]
+  }
+
+  const formatParams = () => {
+    const databaseProperties = {} as { [key: string]: string }
+    state.stepTwo.config.forEach((item: { key: string; value: string }) => {
+      databaseProperties[item.key] = item.value
+    })
+    return {
+      datasourceId: state.stepOne.datasourceId,
+      datasourceName: state.stepOne.datasourceName || '',
+      pluginName: state.stepOne.pluginName || '',
+      tableName: state.stepOne.tableName,
+      //tableFields: state.stepTwo.list.map((item) => ({
+      //  ...omit(item, ['key', 'isEdit']),
+      //  nullable: Boolean(item.nullable),
+      //  primaryKey: Boolean(item.nullable)
+      //})),
+      databaseProperties,
+      databaseName: 'default'
+    }
+  }
+
+  const createOrUpdate = async () => {
+    const values = formatParams()
+    if (state.saving) return false
+    state.saving = true
+
+    try {
+      //id
+      //  ? await updateVirtualTable(values, id)
+      //  : await createVirtualTable(values)
+
+      state.saving = false
+      router.push({
+        name: 'datasource-list',
+        query: { tab: 'virtual-tables' }
+      })
+      return true
+    } catch (err) {
+      state.saving = false
+      return false
+    }
+  }
+
+  const onAddRecord = () => {
+    //state.stepTwo.list.unshift({
+    //  ...defaultRecord,
+    //  key: Date.now() + Math.random() * 1000
+    //})
+  }
+
+  const onChangeStep = async (step: -1 | 1) => {
+    if (state.current === 1 && step === 1) {
+      state.goNexting = true
+      try {
+        await stepOneFormRef.value.validate()
+        await stepTwoFormRef.value.getFormItems(
+          state.stepOne.pluginName,
+          state.stepOne.datasourceName
+        )
+        if (tempDatabaseProperties)
+          stepTwoFormRef.value.setValues(tempDatabaseProperties)
+      } finally {
+        state.goNexting = false
+      }
+    }
+    if (state.current === 2 && step === 1) {
+      state.goNexting = true
+      try {
+        const values = await stepTwoFormRef.value.getValues()
+        state.stepTwo.config = values
+        //const flag = state.stepTwo.list.some((item) => item.isEdit)
+        //if (flag) {
+        //  window.$message.error(t('virtual_tables.save_data_tips'))
+        //  return
+        //}
+        //if (state.stepTwo.list.length === 0) {
+        //  window.$message.error(t('virtual_tables.table_data_required_tips'))
+        //  return
+        //}
+      } finally {
+        state.goNexting = false
+      }
+    }
+    state.current += step
+  }
+
+  onMounted(() => {
+    if (id) {
+      queryById()
+    }
+    queryFieldsType()
+  })
+
+  return {
+    state,
+    stepOneFormRef,
+    stepTwoFormRef,
+    createOrUpdate,
+    onAddRecord,
+    onChangeStep
+  }
+}

Reply via email to