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

xxyu pushed a commit to branch kylin5
in repository https://gitbox.apache.org/repos/asf/kylin.git

commit 6c9afacfa3c6b07c6cc5809b0dea9e77e7f9a6a6
Author: Qian Xia <lauraxiaq...@gmail.com>
AuthorDate: Fri Aug 4 15:23:40 2023 +0800

    KYLIN-5679 table index issue
---
 kystudio/package.json                              |    1 +
 kystudio/src/assets/styles/main.less               |    2 +-
 kystudio/src/assets/styles/variables.less          |    6 +
 .../common/CustomTransferData/TransferData.vue     | 1196 ++++++++++++++++++++
 .../src/components/common/EmptyData/EmptyData.vue  |    2 +-
 .../RecognizeAggregateModal.vue                    |    2 +-
 .../StudioModel/ModelList/TableIndexView/index.vue |    3 +-
 .../studio/StudioModel/TableIndexEdit/locales.js   |   30 +-
 .../studio/StudioModel/TableIndexEdit/store.js     |    3 +-
 .../StudioModel/TableIndexEdit/tableindex_edit.vue |  478 ++++----
 kystudio/src/locale/en.js                          |    1 +
 11 files changed, 1466 insertions(+), 258 deletions(-)

diff --git a/kystudio/package.json b/kystudio/package.json
index 92b45374a8..c8760131dc 100644
--- a/kystudio/package.json
+++ b/kystudio/package.json
@@ -35,6 +35,7 @@
     "nvd3": "1.8.4",
     "smooth-scrollbar": "7.4.0",
     "sql-formatter": "8.2.0",
+    "sortablejs": "1.15.0",
     "uuid": "8.3.2",
     "vue": "2.5.2",
     "vue-clipboard2": "0.0.6",
diff --git a/kystudio/src/assets/styles/main.less 
b/kystudio/src/assets/styles/main.less
index 18829aa96c..5275d7d2b1 100644
--- a/kystudio/src/assets/styles/main.less
+++ b/kystudio/src/assets/styles/main.less
@@ -238,7 +238,7 @@ i[class^=el-icon-]{
 
 .ksd-title-label-mini {
   font-size: 12px;
-  line-height: 16px;
+  line-height: 18px;
   font-weight: @font-medium;
 }
 .el-form-item__label {
diff --git a/kystudio/src/assets/styles/variables.less 
b/kystudio/src/assets/styles/variables.less
index 3c9610b9cf..76029bac76 100644
--- a/kystudio/src/assets/styles/variables.less
+++ b/kystudio/src/assets/styles/variables.less
@@ -57,6 +57,9 @@
 @ke-background-color-light: @background-color-light; //#F9FBFC;
 @ke-background-color-regular: @background-color-regular; //#CDE7F8;
 
+@ke-background-color-drag: #F1F6FE;
+@ke-drag-box-shadow: 0px 2px 8px rgba(50, 73, 107, 0.24);
+
 /* Border
 -------------------------- */
 @ke-border-divider-color: @border-divider-color; //#ECF0F8;
@@ -64,6 +67,9 @@
 @ke-border-secondary-hover: @border-secondary-hover; //#CED6E4;
 @ke-border-secondary-active: @border-secondary-active; //#A5B2C5;
 
+@ke-border-drag: #D5E4FE;
+@ke-border-drag-target: #B0CCFB;
+
 // 纯色
 @fff:#fff;
 @000:#000;
diff --git a/kystudio/src/components/common/CustomTransferData/TransferData.vue 
b/kystudio/src/components/common/CustomTransferData/TransferData.vue
new file mode 100644
index 0000000000..a76b341141
--- /dev/null
+++ b/kystudio/src/components/common/CustomTransferData/TransferData.vue
@@ -0,0 +1,1196 @@
+<template>
+    <div class="sec-index-container">
+      <el-alert v-for="(it, index) in alertItems" :key="index" 
:title="it.text" :type="it.type" show-icon class="ksd-mb-8" 
:closable="false"></el-alert>
+      <div class="transfer-container">
+        <div class="left_layout" :style="leftStyle">
+          <div class="search_layout">
+            <el-input size="small" 
:placeholder="$t('kylinLang.common.search')" v-model.trim="searchVar" 
prefix-icon="el-ksd-n-icon-search-outlined"></el-input>
+          </div>
+          <div class="sec-index-content">
+            <div class="dropdowns">
+              <template v-if="filterModelColumns.length">
+                <el-checkbox class="select-all" v-if="isAllSelect"  
:value="isSelectAll" :indeterminate="filterTypeOptionSelectedColumns.length !== 
0 && filterModelColumns.length > filterTypeOptionSelectedColumns.length" 
@change="selectAll">
+                  <span>{{$t('kylinLang.common.selectAll')}}</span>
+                </el-checkbox>
+                <span v-if="searchVar" class="disable-block">
+                  <el-tooltip placement="top" class="sort-colum-dropdown" 
:content="$t('disableSortFilterTips')">
+                    <el-tag is-light :type="!columnSort ? 'info' : ''"><i 
class="el-ksd-n-icon-table-rank-filled 
ksd-mr-2"></i><span>{{$t('column')}}{{columnSort ? `: ${$t(columnSort)}` : 
''}}</span></el-tag>
+                  </el-tooltip>
+                  <el-tooltip placement="top" class="column-type-dropdown" 
:content="$t('disableSortFilterTips')">
+                    <el-tag is-light :type="!typeOptions.length ? 'info' : 
''"><i class="el-ksd-n-icon-filter-outlined 
ksd-mr-2"></i><span>{{$t('type')}}{{typeOptions.length ? `: 
${typeOptions.length}` : ''}}</span></el-tag>
+                  </el-tooltip>
+                </span>
+                <span v-else>
+                  <el-dropdown class="sort-colum-dropdown" 
placement="bottom-start" @command="handleSortCommand" trigger="click">
+                    <el-tag is-light :type="!columnSort ? 'info' : ''"><i 
class="el-ksd-n-icon-table-rank-filled 
ksd-mr-2"></i><span>{{$t('column')}}{{columnSort ? `: ${$t(columnSort)}` : 
''}}</span></el-tag>
+                    <el-dropdown-menu slot="dropdown" :append-to-body="false">
+                      <el-dropdown-item :class="{'is-active': columnSort === 
'ascending'}" command="ascending">{{$t('ascending')}}</el-dropdown-item>
+                      <el-dropdown-item :class="{'is-active': columnSort === 
'descending'}" command="descending">{{$t('descending')}}</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </el-dropdown>
+                  <el-dropdown class="column-type-dropdown" 
placement="bottom-start" :hide-on-click="false" trigger="click">
+                    <el-tag is-light :type="!typeOptions.length ? 'info' : 
''"><i class="el-ksd-n-icon-filter-outlined 
ksd-mr-2"></i><span>{{$t('type')}}{{typeOptions.length ? `: 
${typeOptions.length}` : ''}}</span></el-tag>
+                    <el-dropdown-menu slot="dropdown" :append-to-body="false">
+                      <el-checkbox-group v-model="typeOptions">
+                        <el-dropdown-item v-for="(item, index) in columnTypes" 
:key="index"><el-checkbox :label="item" 
size="small">{{item}}</el-checkbox></el-dropdown-item>
+                      </el-checkbox-group>
+                    </el-dropdown-menu>
+                  </el-dropdown>
+                </span>
+                <template v-if="isTextRecognition">
+                  <span class="dive ksd-ml-4 ksd-mr-6">|</span>
+                  <el-tooltip placement="top" 
:content="$t('textRecognitionTips')">
+                    <el-button size="small" nobg-text 
icon="el-ksd-n-icon-view-outlined" @click="handleTableIndexRecognize">{{ 
$t('textRecognition') }}</el-button>
+                  </el-tooltip>
+                </template>
+              </template>
+              
+            </div>
+            <div class="result-content" :class="{'is-no-footer': 
!hasLeftFooter}">
+              <div v-for="item in pagedModelColumns" :key="item.id" 
class="result-content_item">
+                <div class="checkbox-list">
+                  <el-checkbox v-model="item.selected" 
:disabled="item.disabled" @change="handleIndexColumnChange(item)">
+                    <el-tooltip :content="$t('excludedTableIconTip')" 
effect="dark" placement="top"><i class="excluded_table-icon 
el-icon-ksd-exclude" v-if="item.excluded"></i></el-tooltip>
+                    <span :style="{'max-width': dragData.width - 140 + 'px', 
width: 'auto'}" :title="item.label" class="ksd-nobr-text">{{item.label}}</span>
+                  </el-checkbox>
+                </div>
+                <div class="item-type">{{item.type}}</div>
+                <div class="hover-icons">
+                  <el-tooltip placement="top">
+                    <div slot="content" style="word-break: break-all">
+                      <div v-if="item.name">{{ 
$t('kylinLang.dataSource.dimensionName') }}: {{ item.name }}</div>
+                      <div>{{ $t('kylinLang.model.columnName') }}: {{ 
item.label }}</div>
+                      <div v-if="item.cardinality">{{ $t('cardinality') }}: {{ 
item.cardinality }}</div>
+                      <div v-if="item.comment">{{ 
$t('kylinLang.dataSource.comment') }}: {{ item.comment }}</div>
+                    </div>
+                    <i class="el-ksd-n-icon-info-circle-outlined"></i>
+                  </el-tooltip>
+                </div>
+              </div>
+              <!-- <div class="load-more-layout" v-if="showLoadMore('left')" 
@click="addPagination('left')">{{$t('kylinLang.common.loadMore')}}</div> -->
+            </div>
+            <empty-data size="small" 
:image="require('../../../assets/img/empty/empty_state_not_found.svg')" 
:content="$t('kylinLang.common.noData')" v-if="!pagedModelColumns.length" />
+            <div class="transfer-footer" v-if="hasLeftFooter">
+              <slot name="left-footer"></slot>
+            </div>
+          </div>
+        </div>
+        <div class="drag-line" unselectable="on" 
v-drag:change.width="dragData">
+          <div class="ky-drag-layout-line"></div>
+        </div>
+        <div class="right_layout">
+          <div class="title-layout">
+            <p class="title">{{rightTitle}}</p>
+            <slot name="help"></slot>
+          </div>
+          <div class="search_layout" v-if="showRightSearch">
+            <el-input size="small" 
:placeholder="$t('kylinLang.common.search')" v-model.trim="searchUsedContent" 
prefix-icon="el-ksd-n-icon-search-outlined"></el-input>
+          </div>
+          <div v-show="searchVar&&pagedSelectedColumns.length" 
class="search-num">{{ filterSelectedColumns.length }}{{ $t('searchResultes') 
}}</div>
+          <div :class="['selected-results', {'over-limit': showOverLimit && 
selectedColumns.length > limitLen}]">
+            <div class="dropdowns" v-show="pagedSelectedColumns.length && 
isSortAble">
+              <span v-if="searchVar || !selectedData.length || 
!isAnyCardinalityCol" class="disable-block">
+                <el-tooltip placement="top" :disabled="!selectedData.length" 
class="sort-colum-dropdown">
+                  <div slot="content" v-if="searchVar">{{ 
$t('disableSortFilterTips') }}</div>
+                  <div slot="content" v-else>{{ $t('disabledSortTips') }} <a 
class="ky-a-like" @click="goToDataSource">{{ $t('goToDataSource') }}</a></div>
+                  <el-tag is-light :type="!selectedColumnSortByCardinality ? 
'info' : ''">
+                    <i class="el-ksd-n-icon-table-rank-filled 
ksd-mr-2"></i><span>{{$t('cardinality')}}{{selectedColumnSortByCardinality ? `: 
${$t(selectedColumnSortByCardinality)}` : ''}}</span>
+                    <el-tooltip placement="top" v-if="isPartCardinalityCol" 
:content="$t('isPartCardinalityCol')">
+                      <i class="el-ksd-n-icon-warning-filled ksd-fs-12"></i>
+                    </el-tooltip>
+                  </el-tag>
+                </el-tooltip>
+              </span>
+              <span v-else>
+                <el-dropdown class="sort-colum-dropdown" 
placement="bottom-start" @command="handleSortCardinality" trigger="click">
+                  <el-tag is-light :type="!selectedColumnSortByCardinality ? 
'info' : ''">
+                    <i class="el-ksd-n-icon-table-rank-filled 
ksd-mr-2"></i><span>{{$t('cardinality')}}{{selectedColumnSortByCardinality ? `: 
${$t(selectedColumnSortByCardinality)}` : ''}}</span>
+                    <el-tooltip placement="top" v-if="isPartCardinalityCol" 
:content="$t('isPartCardinalityCol')">
+                      <i class="el-ksd-n-icon-warning-filled ksd-fs-12"></i>
+                    </el-tooltip>
+                  </el-tag>
+                  <el-dropdown-menu slot="dropdown" :append-to-body="false">
+                    <el-dropdown-item :class="{'is-active': 
selectedColumnSortByCardinality === 'ascending'}" 
command="ascending">{{$t('ascending')}}</el-dropdown-item>
+                    <el-dropdown-item :class="{'is-active': 
selectedColumnSortByCardinality === 'descending'}" 
command="descending">{{$t('descending')}}</el-dropdown-item>
+                  </el-dropdown-menu>
+                </el-dropdown>
+              </span>
+              <span v-if="searchVar || !selectedData.length" 
class="disable-block">
+                <el-tooltip placement="top" class="sort-colum-dropdown" 
:disabled="!selectedData.length" :content="$t('disableSortFilterTips')">
+                  <el-tag is-light :type="!selectedColumnSort ? 'info' : 
''"><i class="el-ksd-n-icon-table-rank-filled 
ksd-mr-2"></i><span>{{$t('column')}}{{selectedColumnSort ? `: 
${$t(selectedColumnSort)}` : ''}}</span></el-tag>
+                </el-tooltip>
+              </span>
+              <span v-else>
+                <el-dropdown class="sort-colum-dropdown" 
placement="bottom-start"  @command="handleSortSelectedCommand" trigger="click">
+                  <el-tag is-light :type="!selectedColumnSort ? 'info' : 
''"><i class="el-ksd-n-icon-table-rank-filled 
ksd-mr-2"></i><span>{{$t('column')}}{{selectedColumnSort ? `: 
${$t(selectedColumnSort)}` : ''}}</span></el-tag>
+                  <el-dropdown-menu slot="dropdown" :append-to-body="false">
+                    <el-dropdown-item :class="{'is-active': selectedColumnSort 
=== 'ascending'}" command="ascending">{{$t('ascending')}}</el-dropdown-item>
+                    <el-dropdown-item :class="{'is-active': selectedColumnSort 
=== 'descending'}" command="descending">{{$t('descending')}}</el-dropdown-item>
+                  </el-dropdown-menu>
+                </el-dropdown>
+              </span>
+            </div>
+            <div class="selected-results_layout" :class="{'is-no-footer': 
!(showOverLimit && selectedColumns.length > limitLen) && !(draggable && 
searchVar), 'is-search-mode': searchVar}">
+              <div :class="['selected-results_item', {'drag-item': 
draggable&&!searchVar, 'is-shared-col': it.isShared}]" v-for="it in 
pagedSelectedColumns" :key="it.key" :id="it.key">
+                <p class="label">
+                  <span class="num" :class="{'is-alive': isShowNum, 
'is-disable-drag': isShowNum&&draggable&&searchVar}" :style="{'scale': 
((getIndex(it.key) + 1) + '').length > 2 ? 1 - 0.2 * (((getIndex(it.key) + 1) + 
'').length - 2) : 1}" v-if="isShowNum">{{ getIndex(it.key) + 1 }}</span>
+                  <el-tooltip placement="top" :content="$t('dragToMove')" 
v-show="draggable&&!searchVar">
+                    <i class="el-ksd-n-icon-grab-dots-outlined" 
:class="{'is-alive': draggable&&!searchVar}"></i>
+                  </el-tooltip><el-tooltip 
:content="$t('excludedTableIconTip')" effect="dark" placement="top"><i 
class="excluded_table-icon el-icon-ksd-exclude" 
v-if="it.excluded"></i></el-tooltip>
+                  <el-button type="primary" size="mini" class="shardby 
is-shardby" v-if="it.isShared" text 
icon="el-ksd-n-icon-symbol-s-circle-filled"></el-button>
+                  <span class="top-icon ignore-drag" 
v-if="topColAble"><el-button type="primary" size="mini" v-if="getIndex(it.key) 
!== 0" text @click.native="topColumn($event, it.key)" 
icon="el-ksd-n-icon-top-filled"></el-button></span>
+                  <span class="ksd-nobr-text">{{it.label}}</span>
+                </p>
+                <div class="right-actions">
+                  <el-tooltip placement="top" :content="$t('cardinality')">
+                    <span class="cardinality ksd-nobr-text" 
@mouseenter.stop>{{it.cardinality}}</span>
+                  </el-tooltip>
+                </div>
+                <div class="hover-icons ignore-drag">
+                  <el-tooltip placement="top">
+                    <div slot="content" style="word-break: break-all">
+                      <div v-if="it.name">{{ 
$t('kylinLang.dataSource.dimensionName') }}: {{ it.name }}</div>
+                      <div>{{ $t('kylinLang.model.columnName') }}: {{ it.label 
}}</div>
+                      <div v-if="it.cardinality">{{ $t('cardinality') }}: {{ 
it.cardinality }}</div>
+                      <div v-if="it.comment">{{ 
$t('kylinLang.dataSource.comment') }}: {{ it.comment }}</div>
+                    </div>
+                    <i class="el-ksd-n-icon-info-circle-outlined ksd-ml-5"></i>
+                  </el-tooltip>
+                  <el-tooltip placement="top" v-if="!it.isShared&sharedByAble" 
:content="shardByIndex !== -1 ? $t('replaceShardBy') : $t('setShardBy')">
+                    <el-button type="primary" size="mini" class="shardby"  
@click.stop="setShardBy(it.key)" text 
icon="el-ksd-n-icon-symbol-s-circle-filled"></el-button>
+                  </el-tooltip><el-tooltip placement="top" 
v-if="it.isShared&sharedByAble"  :content="$t('removeShardBy')">
+                    <el-button type="primary" size="mini" class="shardby 
is-shardby" text @click.stop="unSetSharyBy(it.key)" 
icon="el-ksd-n-icon-symbol-s-circle-filled"></el-button>
+                  </el-tooltip><el-tooltip placement="top" 
:content="$t('remove')">
+                    <i class="el-ksd-n-icon-close-outlined close-btn" 
@click.stop="removeColumn(it)"></i>
+                  </el-tooltip>
+                </div>
+              </div>
+              <!-- <div class="load-more-layout" v-if="showLoadMore('right')" 
@click="addPagination('right')">{{$t('kylinLang.common.loadMore')}}</div> -->
+            </div>
+            <empty-data size="small" 
:image="require('../../../assets/img/empty/empty_state_not_found.svg')" 
:content="searchVar ? $t('kylinLang.common.noData') : rightTitleTip" 
v-if="!pagedSelectedColumns.length" />
+          </div>
+          <div class="over-limit-tips" v-if="showOverLimit && 
selectedColumns.length > limitLen"><el-tooltip placement="top" 
:content="$t('limitTooltip')"><span class="text"><el-icon class="ksd-mr-4" 
name="el-ksd-n-icon-warning-color-filled" 
type="mult"></el-icon>{{$t('overLimitTips')}}</span></el-tooltip><div 
class="line"></div></div>
+          <div class="over-limit-tips" v-if="draggable && searchVar"><span 
class="text"><el-icon class="ksd-mr-4" 
name="el-ksd-n-icon-warning-color-filled" 
type="mult"></el-icon>{{$t('disableDragtips')}}</span><div 
class="line"></div></div>
+        </div>
+      </div>
+      <div class="search-results_layout">
+        <p class="search-results_item">{{$t('selectedNum', {num: 
selectedData.length})}}</p>
+        <span class="dive ksd-ml-12 ksd-mr-12">|</span>
+        <p class="search-results_item">{{$t('allResultNum', {num: 
unSelectedData.length})}}</p>
+      </div>
+    </div>
+  </template>
+  <script>
+  import { Component, Vue } from 'vue-property-decorator'
+  import { objectClone, indexOfObjWithSomeKey, kapConfirm } from 'util'
+  import Sortable, { AutoScroll } from 
'sortablejs/modular/sortable.core.esm.js'
+  import EmptyData from '../EmptyData/EmptyData'
+  
+  Sortable.mount(new AutoScroll())
+  
+  @Component({
+    props: {
+      allModelColumns: {
+        type: Array,
+        default () {
+          return []
+        }
+      },
+      isShowNum: {
+        type: Boolean,
+        default: false
+      },
+      draggable: {
+        type: Boolean,
+        default: false
+      },
+      isSortAble: {
+        type: Boolean,
+        default: false
+      },
+      sharedByAble: {
+        type: Boolean,
+        default: false
+      },
+      topColAble: {
+        type: Boolean,
+        default: false
+      },
+      selectedColumns: {
+        type: Array,
+        default () {
+          return []
+        }
+      },
+      rightTitle: {
+        type: String,
+        default: ''
+      },
+      rightTitleTip: {
+        type: String,
+        default: ''
+      },
+      showOverLimit: {
+        type: Boolean,
+        default: false
+      },
+      pageSize: {
+        type: Array,
+        default () {
+          return [20, 20]
+        }
+      },
+      showRightSearch: {
+        type: Boolean,
+        default: false
+      },
+      limitLen: {
+        type: Number,
+        default: 3
+      },
+      alertTips: {
+        type: Array,
+        default () {
+          return []
+        }
+      },
+      isTextRecognition: {
+        type: Boolean,
+        default: false
+      },
+      isEdit: {
+        type: Boolean,
+        default: false
+      },
+      isAllSelect: {
+        type: Boolean,
+        default: false
+      }
+    },
+    locales: {
+      en: {
+        ascending: 'ascending',
+        descending: 'descending',
+        column: 'Column',
+        type: 'Type',
+        allResultNum: 'Total {num}',
+        selectedNum: 'Selected {num}',
+        searchResultNum: '{num} Results',
+        overLimitTips: 'Reached the recommended limit',
+        limitTooltip: 'Please limit the number of indexes to 3 or less. Add 
index will increase the cost of building and will not effectively improve query 
performance.',
+        cardinality: 'Cardinality',
+        cardinalityValue: 'Cardinality: {value}',
+        dragToMove: 'Drag to move',
+        textRecognition: 'Text Recognition',
+        setShardBy: 'Set as Shardby',
+        replaceShardBy: 'Replace Shardby',
+        removeShardBy: 'Remove Shardby',
+        moveToTop: 'Move to Top',
+        remove: 'Remove',
+        shardbyTips: 'The shardby column is used to store data in slices, 
which can avoid uneven dispersion of table data, improve parallelism and query 
efficiency.',
+        textRecognitionTips: 'Batch selection of columns by automatic 
recognition of pasted text',
+        disabledSortTips: 'The current data has not been sampled. Please 
sample first and try again.',
+        goToDataSource: 'Go to sampling',
+        excludedTableIconTip: 'Excluded column',
+        topColSuccess: 'Column moved to the top',
+        setShardBySuccess: 'Shardby seted up',
+        unSetShardBySuccess: 'Shardby removed',
+        replaceShardBySuccess: 'Shardby replaced',
+        isPartCardinalityCol: 'Some of the current data is not sampled, so 
sorting will only affect some of the results',
+        searchResultes: ' search results',
+        disableDragtips: 'Cannot drag and drop index in search mode',
+        disableSortFilterTips: 'Cannot filter or sort index in search mode',
+        cofirmRemoveSort: 'Are you sure you want to remove the current sort?'
+      }
+    },
+    components: {
+      EmptyData
+    }
+  })
+  export default class TransferData extends Vue {
+    columnSort = ''
+    selectedColumnSort = ''
+    selectedColumnSortByCardinality = this.isEdit ? '' : 'descending'
+    typeOptions = []
+    searchVar = ''
+    searchUsedContent = ''
+    isSelectAll = false
+    currentDrag = {
+      sourceId: '',
+      targetId: '',
+      sourceElement: null,
+      startClientY: null,
+      directive: ''
+    }
+    pagination = {
+      left: {
+        pageOffset: 0,
+        pageSize: this.pageSize[0] || 20,
+        totalSize: 0
+      },
+      right: {
+        pageOffset: 0,
+        pageSize: this.pageSize[1] || 20,
+        totalSize: 0
+      }
+    }
+    dragData = {
+      width: 411.5,
+      limit: {
+        width: [190, 669]
+      }
+    }
+    hasDragSuccess = false
+  
+    get leftStyle () {
+      return {
+        width: this.dragData.width + 'px'
+      }
+    }
+  
+    get hasLeftFooter () {
+      return !!this.$slots['left-footer'] && this.$slots['left-footer'].length
+    }
+  
+    get alertItems () {
+      let items = []
+      if (this.alertTips && this.alertTips.length > 0) {
+        this.alertTips.forEach(item => {
+          if (Object.prototype.toString.call(item) !== '[object Object]') {
+            items.push({ text: item, type: 'warning' })
+          } else {
+            if ((item.checkFn && item.checkFn()) || !item.checkFn) {
+              items.push(item)
+            }
+          }
+        })
+      }
+      return items
+    }
+  
+    get columnTypes () {
+      return [...new Set(this.allModelColumns.filter(it => it.type).map(it => 
it.type))]
+    }
+  
+    get pagedModelColumns () {
+      return this.filterModelColumns.slice(0, (this.pagination.left.pageOffset 
+ 1) * this.pagination.left.pageSize)
+    }
+
+    get filterTypeOptionSelectedColumns () {
+      return this.typeOptions.length ? this.filterSelectedColumns.filter(it => 
this.typeOptions.includes(it.type)) : this.filterSelectedColumns
+    }
+  
+    getIndex (key) {
+      return indexOfObjWithSomeKey(this.selectedData, 'key', key)
+    }
+  
+    get pagedSelectedColumns () {
+      return this.filterSelectedColumns.slice(0, 
(this.pagination.right.pageOffset + 1) * this.pagination.right.pageSize)
+    }
+  
+    get filterSelectedColumns () {
+      let selectedColumns = objectClone(this.selectedData)
+      if (this.searchVar) {
+        this.$set(this.pagination.right, 'pageOffset', 0)
+        selectedColumns = this.selectedData.filter(it => 
it.label.toLocaleLowerCase().indexOf(this.searchVar.toLocaleLowerCase()) > -1)
+      }
+      return selectedColumns
+    }
+  
+    get filterModelColumns () {
+      // let allModelColumns = this.allModelColumns.filter(it => 
!this.selectedColumns.includes(it.key)).map(item => ({...item, selected: 
false}))
+      let allModelColumns = this.allModelColumns.map(item => ({...item, 
selected: this.selectedColumns.includes(item.key)}))
+      let resultDatas = []
+      if (this.searchVar) {
+        this.$set(this.pagination.left, 'pageOffset', 0)
+        allModelColumns = allModelColumns.filter(it => 
it.label.toLocaleLowerCase().indexOf(this.searchVar.toLocaleLowerCase()) > -1)
+      }
+      if (!this.typeOptions.length) {
+        if (!this.columnSort) {
+          resultDatas = allModelColumns
+        } else {
+          resultDatas = this.columnSort === 'ascending' ? 
allModelColumns.sort((a, b) => a.label.localeCompare(b.label)) : 
allModelColumns.sort((a, b) => b.label.localeCompare(a.label))
+        }
+      } else {
+        this.$set(this.pagination.left, 'pageOffset', 0)
+  
+        // const selectedColumns = allModelColumns.filter(it => 
this.selectedColumns.includes(it.key))
+        const results = [...allModelColumns.filter(it => 
this.typeOptions.includes(it.type))]
+        if (!this.columnSort) {
+          resultDatas = results
+        } else {
+          resultDatas = this.columnSort === 'ascending' ? results.sort((a, b) 
=> a.label.localeCompare(b.label)) : results.sort((a, b) => 
b.label.localeCompare(a.label))
+        }
+      }
+      this.$set(this.pagination.left, 'totalSize', resultDatas.length)
+      return resultDatas
+    }
+  
+    get isAnyCardinalityCol () {
+      return this.selectedData.length && this.selectedData.filter((c) => 
!!c.cardinality).length > 0
+    }
+  
+    get isPartCardinalityCol () {
+      const cardinalityNum = this.selectedData.filter((c) => 
!!c.cardinality).length
+      return this.selectedData.length && cardinalityNum > 0 && cardinalityNum 
< this.selectedData.length
+    }
+  
+    get unSelectedData () {
+      return this.allModelColumns
+    }
+  
+    get selectedData () {
+      let sData = []
+      if (this.selectedColumns.length) {
+        this.selectedColumns.forEach(key => {
+          const [obj] = this.allModelColumns.filter(item => item.key === key)
+          obj && sData.push(obj)
+        })
+        if (this.isSortAble) {
+          if (this.searchUsedContent) {
+            this.$set(this.pagination.right, 'pageOffset', 0)
+            sData = sData.filter(it => 
it.label.toLocaleLowerCase().indexOf(this.searchUsedContent.toLocaleLowerCase())
 >= 0)
+          }
+          if (this.selectedColumnSort) {
+            sData = this.selectedColumnSort === 'ascending' ? sData.sort((a, 
b) => a.label.localeCompare(b.label)) : sData.sort((a, b) => 
b.label.localeCompare(a.label))
+          } else if (this.selectedColumnSortByCardinality) {
+            sData = this.selectedColumnSortByCardinality === 'ascending' ? 
sData.sort((a, b) => a.cardinality - b.cardinality) : sData.sort((a, b) => 
b.cardinality - a.cardinality)
+          }
+        }
+      }
+      this.$set(this.pagination.right, 'totalSize', sData.length)
+      return sData
+    }
+  
+    get shardByIndex () {
+      return indexOfObjWithSomeKey(this.selectedData, 'isShared', true)
+    }
+  
+    created () {
+      this.pagination.left.totalSize = this.filterModelColumns.length
+      this.pagination.right.totalSize = this.selectedData.length
+    }
+  
+    mounted () {
+      const $unSelectPage = this.$el.querySelector('.result-content')
+      const $selectedPage = this.$el.querySelector('.selected-results_layout')
+      $unSelectPage && $unSelectPage.addEventListener('scroll', 
this.debounce(this.handleScroll, '.result-content', 'left', 200))
+      $selectedPage && $selectedPage.addEventListener('scroll', 
this.debounce(this.handleScroll, '.selected-results_layout', 'right', 200))
+      if (this.draggable) {
+        const rightPanel = this.$el.querySelector('.selected-results')
+        const rightEl = $selectedPage
+        Sortable.create(rightEl, {
+          scroll: true,
+          handle: '.drag-item',
+          filter: '.ignore-drag',
+          animation: 150,
+          scrollSensitivity: 50,
+          scrollSpeed: 10,
+          bubbleScroll: true,
+          forceFallback: true,
+          revert: true,
+          scrollFn: () => {
+            return 'continue'
+          },
+          ghostClass: 'sortable-ghost',
+          dragClass: 'sortable-drag',
+          onEnd: async (e) => {
+            if (this.selectedColumnSortByCardinality || 
this.selectedColumnSort) {
+              try {
+                await kapConfirm(' ', {confirmButtonText: this.$t('remove'), 
type: 'warning'}, this.$t('cofirmRemoveSort'))
+              } catch (res) {
+                const items = this.$el.querySelectorAll('.drag-item')
+                this.$nextTick(() => {
+                  e.from.insertBefore(e.item, items[e.oldIndex + (e.oldIndex > 
e.newIndex)])
+                })
+                return false
+              }
+            }
+            const { oldIndex, newIndex } = e
+            let selectedList = this.selectedData.map(d => d.key)
+            const temp = selectedList[oldIndex]
+            if (temp === undefined) {
+              return
+            }
+            const target = selectedList.splice(oldIndex, 1)[0]
+            selectedList.splice(newIndex, 0, target)
+            this.selectedColumnSortByCardinality = ''
+            this.selectedColumnSort = ''
+            this.hasDragSuccess = true
+            this.handleEmitEvent('setSelectedColumns', selectedList)
+          }
+        })
+        rightPanel.ondragover = (e) => {
+          e.preventDefault()
+        }
+        rightPanel.ondrop = (e) => {
+          e.preventDefault()
+        }
+      }
+    }
+  
+    debounce (fn, dom, scrollType, delay) {
+      let timer = null
+      return function () {
+        if (timer) {
+          clearTimeout(timer)
+        }
+        timer = setTimeout(() => {
+          fn(dom, scrollType)
+        }, delay)
+      }
+    }
+  
+    destroyed () {
+      const $unSelectPage = this.$el.querySelector('.result-content')
+      const $selectedPage = this.$el.querySelector('.selected-results_layout')
+      $unSelectPage && $unSelectPage.removeEventListener('scroll', 
this.handleScroll)
+      $selectedPage && $selectedPage.removeEventListener('scroll', 
this.handleScroll)
+    }
+  
+    handleScroll (dom, scrollType) {
+      const scrollBoxDom = this.$el.querySelector(dom)
+      const scrollT = scrollBoxDom.scrollTop
+      const scrollH = scrollBoxDom.scrollHeight
+      const clientH = scrollBoxDom.clientHeight
+      if (scrollT + scrollH >= clientH) {
+        this.scrollLoad(scrollType)
+      }
+    }
+  
+    handleTableIndexRecognize () {
+      this.$emit('handleTableIndexRecognize')
+    }
+  
+    selectAll () {
+      let selectedList = objectClone(this.selectedColumns)
+      // 除去disabled的列,
+      this.isSelectAll = this.filterModelColumns.filter(c => 
!c.disabled).length > this.filterSelectedColumns.length
+      this.filterModelColumns.forEach((c) => {
+        !c.disabled && (c.selected = this.isSelectAll)
+        if (this.isSelectAll) {
+          const index = selectedList.indexOf(c.key)
+          index === -1 && !c.disabled && selectedList.push(c.key)
+        } else {
+          const index = selectedList.indexOf(c.key)
+          index !== -1 && !c.disabled && selectedList.splice(index, 1)
+        }
+      })
+      this.handleEmitEvent('setSelectedColumns', selectedList)
+      this.setSelectedColumns()
+    }
+  
+    showLoadMore (type) {
+      const { left, right } = this.pagination
+      if (type === 'left') {
+        return (left.pageOffset + 1) * left.pageSize < left.totalSize
+      } else {
+        return (right.pageOffset + 1) * right.pageSize < right.totalSize
+      }
+    }
+  
+    scrollLoad (type) {
+      if (this.showLoadMore(type)) {
+        this.addPagination(type)
+      }
+    }
+  
+    addPagination (type) {
+      const page = this.pagination[type].pageOffset + 1
+      this.$set(this.pagination[type], 'pageOffset', page)
+    }
+  
+    handleEmitEvent (name, value) {
+      this.$emit(name, value)
+    }
+  
+    handleSortCommand (sort) {
+      this.columnSort = sort
+    }
+  
+    setSelectedColumns () {
+      this.$nextTick(() => {
+        const selectedList = this.selectedData.map(d => d.key)
+        this.handleEmitEvent('setSelectedColumns', selectedList)
+      })
+    }
+  
+    async handleSortSelectedCommand (sort) {
+      if (this.hasDragSuccess) {
+        await kapConfirm(' ', {confirmButtonText: this.$t('remove'), type: 
'warning'}, this.$t('cofirmRemoveSort'))
+      }
+      this.selectedColumnSortByCardinality = ''
+      this.selectedColumnSort = sort
+      this.setSelectedColumns()
+      this.hasDragSuccess = false
+    }
+  
+    async handleSortCardinality (sort) {
+      if (this.hasDragSuccess) {
+        await kapConfirm(' ', {confirmButtonText: this.$t('remove'), type: 
'warning'}, this.$t('cofirmRemoveSort'))
+      }
+      this.selectedColumnSort = ''
+      this.selectedColumnSortByCardinality = sort
+      this.setSelectedColumns()
+      this.hasDragSuccess = false
+    }
+  
+    goToDataSource () {
+      this.$router.push('/studio/source')
+    }
+  
+    handleIndexColumnChange (v) {
+      const selectedList = objectClone(this.selectedColumns)
+      if (v.selected) {
+        selectedList.push(v.key)
+      } else {
+        const index = selectedList.indexOf(v.key)
+        index > -1 && selectedList.splice(index, 1)
+      }
+      this.handleEmitEvent('setSelectedColumns', selectedList)
+      this.setSelectedColumns()
+    }
+  
+    removeColumn (v) {
+      const selectedList = objectClone(this.selectedColumns)
+      const index = selectedList.indexOf(v.key)
+      index > -1 && selectedList.splice(index, 1)
+      this.handleEmitEvent('setSelectedColumns', selectedList)
+    }
+  
+    async topColumn (e, key) {
+      e.stopPropagation()
+      e.preventDefault()
+      if (this.selectedColumnSortByCardinality || this.selectedColumnSort) {
+        try {
+          await kapConfirm(' ', {confirmButtonText: this.$t('remove'), type: 
'warning'}, this.$t('cofirmRemoveSort'))
+        } catch (res) {
+          return
+        }
+      }
+      const selectedList = this.selectedData.map(d => d.key)
+      const index = selectedList.indexOf(key)
+      selectedList.splice(0, 0, selectedList[index])
+      selectedList.splice(index + 1, 1)
+      this.selectedColumnSortByCardinality = ''
+      this.selectedColumnSort = ''
+      this.$nextTick(() => {
+        this.handleEmitEvent('setSelectedColumns', selectedList)
+      })
+      this.hasDragSuccess = true
+      this.$message({ type: 'success', message: this.$t('topColSuccess') })
+    }
+  
+    setShardBy (key) {
+      let message = ''
+      if (this.shardByIndex !== -1) {
+        this.selectedData[this.shardByIndex].isShared = false
+        message = this.$t('replaceShardBySuccess')
+      }
+      const index = indexOfObjWithSomeKey(this.selectedData, 'key', key)
+      this.selectedData[index].isShared = true
+      this.handleEmitEvent('setShardbyCol', this.selectedData[index].label)
+      this.$message({ type: 'success', message: message || 
this.$t('setShardBySuccess') })
+    }
+  
+    unSetSharyBy (key) {
+      const index = indexOfObjWithSomeKey(this.selectedData, 'key', key)
+      this.selectedData[index].isShared = false
+      this.handleEmitEvent('setShardbyCol', '')
+      this.$message({ type: 'success', message: this.$t('unSetShardBySuccess') 
})
+    }
+  }
+  </script>
+  <style lang="less">
+  @import '../../../assets/styles/variables.less';
+  .sec-index-container {
+    width: 100%;
+    height: 450px;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    .hover-icons {
+      display: none;
+      position: absolute;
+      right: 4px;
+      right: 0px;
+      z-index: 999;
+      background-color: @ke-color-info-secondary-bg;
+      padding: 0 4px;
+      .close-btn {
+        color: @text-placeholder-color;
+        cursor: pointer;
+      }
+      .el-ksd-n-icon-info-circle-outlined {
+        font-size: 14px;
+        color: @text-placeholder-color;
+        cursor: pointer;
+      }
+      .shardby.is-sharcby {
+        background-color: @ke-color-success-hover;
+      }
+      .el-button {
+        position: relative;
+        top: -1px;
+      }
+    }
+    .disable-block {
+      .el-tag {
+        margin: 2px 4px;
+        cursor: not-allowed;
+        opacity: 0.3;
+        background-color: @ke-background-color-secondary;
+        &:hover {
+          background-color: @ke-background-color-drag;
+        }
+      }
+    }
+    .search-results_layout {
+      box-sizing: border-box;
+      position: absolute;
+      bottom: 32px;
+      .search-results_item {
+        display: inline-block;
+        color: @text-disabled-color;
+        font-size: 14px;
+        font-weight: 400;
+      }
+    }
+    .drag-line {
+      padding: 0 4px;
+      .ky-drag-layout-line {
+        border-left: 1px solid @ke-border-secondary;
+        height: 100%;
+      }
+      &:hover {
+        cursor: col-resize;
+        .ky-drag-layout-line {
+          border-left: 1px solid @base-color;
+        }
+      }
+    }
+    .dive {
+      color: @ke-border-secondary;
+    }
+    .transfer-container {
+      width: 100%;
+      height: 0;
+      flex: 1;
+      display: flex;
+      border-radius: 6px;
+      border: 1px solid @ke-border-divider-color;
+      .el-transfer {
+        .el-transfer-panel__functions {
+          font-size: 0;
+        }
+      }
+      .column-type-dropdown {
+        .el-checkbox__inner {
+          vertical-align: middle;
+        }
+        .el-dropdown-menu {
+          max-height: 358px;
+          overflow: auto;
+        }
+      }
+    }
+    .left_layout {
+      // width: 50%;
+      height: 100%;
+      // border-right: 1px solid @ke-border-divider-color;
+      display: inline-flex;
+      flex-direction: column;
+      .search_layout {
+        padding: 8px;
+        margin-right: -4px;
+        box-sizing: border-box;
+        border-bottom: 1px solid @ke-border-divider-color;
+        .el-input__inner {
+          border: 0;
+          outline: none;
+          box-shadow: none;
+        }
+      }
+      .sec-index-content {
+        height: 0;
+        display: flex;
+        flex-direction: column;
+        flex: 1;
+        position: relative;
+      }
+      .transfer-footer {
+        height: 28px;
+        padding: 8px 0;
+        border-top: 1px solid @ke-border-divider-color;
+      }
+    }
+    .right_layout {
+      // width: 50%;
+      flex: 1;
+      height: 100%;
+      display: inline-flex;
+      flex-direction: column;
+      vertical-align: top;
+      position: relative;
+      .search-num {
+        margin: 0 auto;
+        text-align: center;
+        color: @text-placeholder-color;
+        font-size: 12px;
+        line-height: 18px;
+        margin-bottom: 16px;
+      }
+      .empty-data {
+        width: 100%;
+        .center {
+          padding: 0 16px;
+        }
+      }
+      .search_layout {
+        padding: 8px 4px;
+        box-sizing: border-box;
+        .el-input__inner {
+          border: 0;
+          outline: none;
+          box-shadow: none;
+        }
+      }
+      .title-layout {
+        display: flex;
+        justify-content: space-between;
+        padding: 17px 8px;
+        box-sizing: border-box;
+        .el-ksd-n-icon-help-circle-outlined {
+          color: @text-placeholder-color;
+          cursor: pointer;
+        }
+      }
+      .selected-results {
+        // height: calc(~'100% - 52px');
+        flex: 1;
+        position: relative;
+        height: 0;
+        display: flex;
+        flex-direction: column;
+        .el-ksd-n-icon-warning-filled {
+          color: @ke-color-warning;
+        }
+        &.over-limit {
+          padding-bottom: 40px;
+          box-sizing: border-box;
+        }
+        .right-actions {
+          max-width: 100px;
+          display: inherit;
+        }
+        .dropdowns {
+          padding: 0;
+          height: 30px;
+        }
+        .selected-results_layout {
+          overflow: auto;
+          height: 320px;
+          margin-left: -4px;
+          &.is-search-mode {
+            height: 286px;
+          }
+          &.is-no-footer {
+            height: 360px;
+            &.is-search-mode {
+              height: 326px;
+            }
+          }
+        }
+        .selected-results_item {
+          line-height: 34px;
+          font-size: 14px;
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          padding: 0 4px 0 4px;
+          box-sizing: border-box;
+          position: relative;
+          color: @text-title-color;
+          cursor: default;
+          &.drag-item {
+            cursor: grab;
+          }
+          .excluded_table-icon {
+            color: @text-placeholder-color;
+            margin-right: 2px;
+          }
+          .label {
+            // user-select: none;
+            flex: 1;
+            width: 0;
+            display: inline-flex;
+            align-items: center;
+            .num.is-alive {
+              display: inline-block;
+              color: @text-placeholder-color;
+              width: 20px;
+              text-align: center;
+            }
+            .el-ksd-n-icon-grab-dots-outlined {
+              display: none;
+            }
+          }
+          &:hover {
+            .num:not(.is-disable-drag) {
+              display: none !important;
+            }
+            .el-ksd-n-icon-grab-dots-outlined.is-alive {
+              display: inline-block !important;
+              width: 20px;
+              text-align: center;
+            }
+          }
+          .el-button--mini {
+            padding: 3px 4px;
+          }
+          .shardby {
+            .el-ksd-n-icon-symbol-s-circle-filled {
+              color: @text-placeholder-color;
+            }
+            &:hover {
+              .el-ksd-n-icon-symbol-s-circle-filled {
+                color: @text-disabled-color;
+              }
+            }
+            &.is-shardby {
+              .el-ksd-n-icon-symbol-s-circle-filled{
+                color: @ke-color-success-hover;
+              }
+              &:hover {
+                .el-ksd-n-icon-symbol-s-circle-filled{
+                  color: @ke-color-success-active;
+                }
+              }
+            }
+          }
+          .el-ksd-n-icon-delete-outlined {
+            position: relative;
+            top: -1px;
+            color: @ke-color-info-secondary;
+            font-size: 16px;
+            margin-right: 2px;
+            &.active {
+              color: @ke-color-success-hover;
+            }
+          }
+          .top-icon {
+            display: none;
+            position: absolute;
+            left: 16px;
+            z-index: 999;
+            background-color: @ke-color-info-secondary-bg;
+            .el-button {
+              position: relative;
+              top: -1px;
+            }
+            .el-ksd-n-icon-top-filled {
+              color: @text-placeholder-color;
+              &:hover {
+                color: @text-disabled-color;
+              }
+            }
+          }
+          &:hover {
+            background: @ke-color-info-secondary-bg;
+            .hover-icons,
+            .top-icon {
+              display: initial;
+            }
+          }
+          &:active {
+            background-color: @ke-background-color-drag;
+            .hover-icons,
+            .top-icon {
+              background-color: @ke-background-color-drag;
+              .el-button {
+                background-color: @ke-background-color-drag;
+              }
+            }
+          }
+          &.is-shared-col {
+            background-color: @ke-color-success-bg;
+            color: @ke-color-success-active;
+            font-weight: @font-medium;
+            .hover-icons,
+            .top-icon {
+              background-color: @ke-color-success-bg;
+              .el-button {
+                background-color: #D9F1D0;
+              }
+            }
+          }
+          .tip_box {
+            height: 32px;
+            vertical-align: middle;
+          }
+          &.sortable-ghost {
+            opacity: 0.1 !important;
+            background-color: @ke-border-drag;
+            border-width: 2px 3px;
+            border-style: dashed;
+            border-color: @ke-border-drag-target;
+          }
+          &.sortable-drag {
+            background-color: @ke-background-color-drag;
+            box-shadow: @ke-drag-box-shadow;
+            border: 1px solid @ke-border-drag;
+            opacity: 0.8;
+            width: 300px !important;
+            cursor: grabbing !important;
+            .cardinality {
+              display: none;
+            }
+          }
+        }
+        .cardinality {
+          color: @text-placeholder-color;
+          user-select: none;
+        }
+        .empty-data-normal {
+          img {
+            height: 64px;
+          }
+        }
+      }
+      .over-limit-tips {
+        width: 100%;
+        height: 32px;
+        text-align: center;
+        font-size: 12px;
+        color: @ke-color-warning-hover;
+        position: absolute;
+        bottom: 0;
+        left: 0;
+        .line {
+          width: 100%;
+          height: 1px;
+          background: #FFDFC2;
+          position: absolute;
+          left: 0;
+          top: 50%;
+          z-index: 1;
+        }
+        .text {
+          display: inline-block;
+          background: @fff;
+          padding: 0 10px;
+          z-index: 2;
+          position: absolute;
+          top: 0;
+          left: 50%;
+          transform: translate(-50%, 0);
+          line-height: 32px;
+          cursor: default;
+          white-space: pre;
+        }
+      }
+    }
+    .load-more-layout {
+      text-align: center;
+      font-size: 12px;
+      cursor: pointer;
+      line-height: 32px;
+      &:hover {
+        color: #1268FB;
+      }
+    }
+    .sec-index-content {
+      padding: 10px 0 0 0;
+      box-sizing: border-box;
+      .result-content {
+        height: 320px;
+        overflow: auto;
+        margin-right: -4px;
+        &.is-no-footer {
+          height: 360px;
+        }
+        .result-content_item {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          overflow-x: hidden;
+          padding: 0 4px 0 8px;
+          box-sizing: border-box;
+          position: relative;
+          &:hover {
+            background: @ke-background-color-secondary;
+            .hover-icons {
+              display: initial !important;
+            }
+          }
+          .excluded_table-icon {
+            position: relative;
+            top: -7px;
+            color: @text-placeholder-color;
+          }
+          .checkbox-list {
+            flex: 1;
+            width: 0;
+          }
+          .el-checkbox {
+            width: 100%;
+            height: 34px;
+            .el-checkbox__label {
+              position: relative;
+              top: 3px;
+            }
+            .el-ksd-n-icon-info-circle-outlined {
+              position: relative;
+              top: -5px;
+              font-size: 14px;
+              color: @text-placeholder-color;
+            }
+          }
+          .item-type {
+            color: @text-placeholder-color;
+          }
+        }
+      }
+    }
+    .flip-list-move {
+      transition: transform .5s;
+    }
+    .dropdowns {
+      padding: 0 8px;
+      display: flex;
+      flex-wrap: wrap;
+      font-size: 0;
+      height: 30px;
+      margin-right: -4px;
+      .select-all {
+        flex-grow: 3;
+      }
+      .el-dropdown {
+        cursor: pointer;
+        margin: 2px 4px;
+        .el-dropdown-menu__item {
+          &.is-active {
+            color: @ke-color-primary;
+          }
+          &.is-disabled {
+            color: @text-placeholder-color;
+            pointer-events: initial;
+          }
+        }
+      }
+      .dive {
+        font-size: 18px;
+      }
+      .el-tag {
+        &.is-light {
+          background-color: #f1f6fe;
+          border-color: #f1f6fe;
+          &:hover {
+            background-color: #d5e4fe;
+            border-color: #d5e4fe;
+          }
+        }
+      }
+    }
+  }
+  </style>
diff --git a/kystudio/src/components/common/EmptyData/EmptyData.vue 
b/kystudio/src/components/common/EmptyData/EmptyData.vue
index ebca39e300..4e855739e7 100644
--- a/kystudio/src/components/common/EmptyData/EmptyData.vue
+++ b/kystudio/src/components/common/EmptyData/EmptyData.vue
@@ -73,7 +73,7 @@ export default class EmptyData extends Vue {
     img {
       height: 60px;
     }
-    font-size: 14px;
+    font-size: 12px;
     .center:first-child {
       margin-bottom: 8px;
     }
diff --git 
a/kystudio/src/components/common/RecognizeAggregateModal/RecognizeAggregateModal.vue
 
b/kystudio/src/components/common/RecognizeAggregateModal/RecognizeAggregateModal.vue
index 43b6dddd6b..7ca329cbbf 100644
--- 
a/kystudio/src/components/common/RecognizeAggregateModal/RecognizeAggregateModal.vue
+++ 
b/kystudio/src/components/common/RecognizeAggregateModal/RecognizeAggregateModal.vue
@@ -473,7 +473,7 @@
         font-weight: 400;
         font-size: 12px;
         line-height: 16px;
-        color: @text-normal-color;
+        color: @text-placeholder-color;
         padding: 0 6px;
         white-space: pre-wrap;
       }
diff --git 
a/kystudio/src/components/studio/StudioModel/ModelList/TableIndexView/index.vue 
b/kystudio/src/components/studio/StudioModel/ModelList/TableIndexView/index.vue
index 42ea5bdca8..0264dfeb15 100644
--- 
a/kystudio/src/components/studio/StudioModel/ModelList/TableIndexView/index.vue
+++ 
b/kystudio/src/components/studio/StudioModel/ModelList/TableIndexView/index.vue
@@ -170,7 +170,8 @@ export default class TableIndexView extends Vue {
     const { isSubmit } = await this.showTableIndexEditModal({
       modelInstance: this.modelInstance,
       tableIndexDesc: indexDesc || {name: 'TableIndex_1'},
-      indexUpdateEnabled: this.indexUpdateEnabled
+      indexUpdateEnabled: this.indexUpdateEnabled,
+      isShowHelp: this.indexDatas.length === 0
     })
     isSubmit && this.loadTableIndices()
     isSubmit && this.$emit('refreshModel')
diff --git 
a/kystudio/src/components/studio/StudioModel/TableIndexEdit/locales.js 
b/kystudio/src/components/studio/StudioModel/TableIndexEdit/locales.js
index 45109240d4..1be1f66508 100644
--- a/kystudio/src/components/studio/StudioModel/TableIndexEdit/locales.js
+++ b/kystudio/src/components/studio/StudioModel/TableIndexEdit/locales.js
@@ -12,7 +12,8 @@ export default {
     catchup: 'Build index now',
     sortLimitTip: 'At most nine \'sort\' column is allowed.',
     sort: 'Order',
-    tableIndex: 'table index',
+    tableIndex: 'Table index',
+    tableIndexTips: 'Table index can be filtered and sorted to prioritize 
queries, effectively improving query performance',
     cofirmEditTableIndex: 'After submitting, the system will create a new 
table index based on your changes. Meanwhile, the original index will be 
changed to "LOCKED" status and still be available for querying. The index will 
be deleted after the new table index is built successfully. Please note that 
the deleted index cannot be recovered. Are you sure you want to submit?',
     saveAndBuild: 'Save and Build',
     cardinality: 'Cardinality',
@@ -22,14 +23,27 @@ export default {
     moveDown: 'Move down',
     tableIndexShardByTips: 'Please select columns for detail query. To enhance 
the query performance, please move the frequently used dimensions to the top of 
the list, and set a column with relatively large cardinality as ShardBy.',
     filterByColumns: 'Search by column name',
-    excludeTableCheckbox: 'Display columns excluded from recommendations',
-    excludeTableCheckboxTip: 'If an excluded column is added to indexes, this 
column will store "historical truth"(SCD Type2, As Was).<br/>If you want to use 
"latest status" (SCD Type 1, As Is) ,please don\'t add this column to any index 
and add foreign key instead.',
-    excludedTableIconTip: 'Excluded from recommendations',
-    manyToManyAntiTableTip: 'For the tables excluded from recommendations, if 
the join relationship of a table is One-to-Many or Many-to-Many, dimensions 
from this table can\'t be used in indexes. ',
-    indexTimeRange: 'Index’s Time Range',
-    indexTimeRangeTips: 'The data range that the indexes will be built in. 
With "Batch and Streaming" selected, there will be generated batch indexes and 
streaming indexes with same content respectively. ',
+    excludeTableCheckbox: 'Display columns excluded',
+    excludeTableCheckboxTip1: 'Exclude columns using the slow-changing 
dimension to record data changes:',
+    excludeTableCheckboxTip2: 'When you want the column to reflect historical 
facts (SCD Type2, As Was), please check this option and add it to the index.',
+    excludeTableCheckboxTip3: 'When you want the column to reflect the latest 
state of the dimension (SCD Type1, As Is), do not check this option. Instead, 
add a foreign key to the index.',
+    excludeTableCheckboxTip4: '<br/>Not familiar with the slow-changing 
dimension? <a class="ky-a-like" 
href="https://docs.kyligence.io/books/v4.6/en/Designers-Guide/model/model_design/slowly_changing_dimension.en.html?h=Slowly%20Changing%20Dimension";
 target="_blank">Check the manual</a>',
+    manyToManyAntiTableTip: 'The table columns excluded, if the join 
relationship of a table is One-to-Many or Many-to-Many, dimensions from this 
table can\'t be used in indexes. ',
+    indexTimeRange: 'Real-time Index Types',
+    indexTimeRangeTips: 'After selection, the table index will generate 
streaming indexes and batch indexes of the same content respectively.',
     noIndexRangeByHybrid: 'Select index’s data range to display available 
columns.',
     textRecognition: 'Text Recognition',
-    refuseAddIndexTip: 'Can\'t add streaming indexes. Please stop the 
streaming job and then delete all the streaming segments.'
+    refuseAddIndexTip: 'Can\'t add streaming indexes. Please stop the 
streaming job and then delete all the streaming segments.',
+    sugessionLabel1: 'Rec.1',
+    sugession1: 'Sort by common usage frequency. Reasonable ordering can 
significantly improve the efficiency of table queries.',
+    sugessionLabel2: 'Rec.2',
+    sugession2: 'Set one high-cardinality column as shardby. The shardby 
column is used to store data in slices, which can avoid uneven dispersion of 
table data, improve parallelism and query efficiency.',
+    tips: 'No cardinality information? Please sample first and try again. ',
+    goToDataSource: 'Go to sampling',
+    knowMore: 'For more information, please ',
+    userManual: 'view the manual',
+    shardbyManal: 
'https://docs.kyligence.io/books/v4.6/en/Designers-Guide/model/model_design/table_index.en.html?q=shardBy',
+    indexRangeReqiured: 'Please select the real-time index type',
+    help: 'Help'
   }
 }
diff --git a/kystudio/src/components/studio/StudioModel/TableIndexEdit/store.js 
b/kystudio/src/components/studio/StudioModel/TableIndexEdit/store.js
index 82e955b2d0..11940d4f9e 100644
--- a/kystudio/src/components/studio/StudioModel/TableIndexEdit/store.js
+++ b/kystudio/src/components/studio/StudioModel/TableIndexEdit/store.js
@@ -14,7 +14,8 @@ const initialState = JSON.stringify({
     data: {
       modelInstance: null,
       tableIndexDesc: null,
-      indexUpdateEnabled: true
+      indexUpdateEnabled: true,
+      isShowHelp: false
     }
   },
   callback: null
diff --git 
a/kystudio/src/components/studio/StudioModel/TableIndexEdit/tableindex_edit.vue 
b/kystudio/src/components/studio/StudioModel/TableIndexEdit/tableindex_edit.vue
index ce19a38b36..e2c07f6792 100644
--- 
a/kystudio/src/components/studio/StudioModel/TableIndexEdit/tableindex_edit.vue
+++ 
b/kystudio/src/components/studio/StudioModel/TableIndexEdit/tableindex_edit.vue
@@ -1,86 +1,94 @@
 <template>
   <!-- tableindex的添加和编辑 -->
-  <el-dialog :title="tableIndexModalTitle" append-to-body limited-area 
top="5vh" class="table-edit-dialog" width="880px" v-if="isShow" :visible="true" 
:close-on-press-escape="false" :close-on-click-modal="false" @close="isShow && 
closeModal()">
-      <div class="table-index-list">
-        <div class="ksd-mb-10" v-if="modelInstance.model_type === 'HYBRID'">
-          <h4>
-            <span class="is-required">*</span>
-            {{$t('indexTimeRange')}}
-            <common-tip :content="$t('indexTimeRangeTips')"><i 
class="el-ksd-icon-more_info_16 ksd-fs-16"></i></common-tip>
-          </h4>
-          <el-radio-group v-model="tableIndexMeta.index_range" 
:disabled="tableIndexMeta.id !== ''" @change="changeTableIndexType">
-            <el-tooltip placement="top" :disabled="indexUpdateEnabled" 
:content="$t('refuseAddIndexTip')">
-              <el-radio :label="'HYBRID'" 
:disabled="!indexUpdateEnabled">{{$t('kylinLang.common.HYBRID')}}</el-radio>
-            </el-tooltip>
-            <el-radio 
:label="'BATCH'">{{$t('kylinLang.common.BATCH')}}</el-radio>
-            <el-tooltip placement="top" :disabled="indexUpdateEnabled" 
:content="$t('refuseAddIndexTip')">
-              <el-radio :label="'STREAMING'" 
:disabled="!indexUpdateEnabled">{{$t('kylinLang.common.STREAMING')}}</el-radio>
+  <el-dialog :title="tableIndexModalTitle" append-to-body limited-area 
class="table-edit-dialog" width="880px" v-if="isShow" :visible="true" 
:close-on-press-escape="false" :close-on-click-modal="false" @close="isShow && 
closeModal()">
+    <div class="table-index-list">
+      <transfer-data
+        :allModelColumns="allColumns"
+        isShowNum
+        draggable
+        topColAble
+        sharedByAble
+        isSortAble
+        isAllSelect
+        :selectedColumns="selectedColumns"
+        :rightTitle="$t('tableIndex')"
+        :rightTitleTip="$t('tableIndexTips')"
+        isTextRecognition
+        :isEdit="this.tableIndexMeta.id !== ''"
+        :alertTips="alertTips"
+        @handleTableIndexRecognize="handleTableIndexRecognize"
+        @setSelectedColumns="(v) => setSelectedColumns(v)"
+        @setShardbyCol="(label) => setShardbyCol(label)">
+        <template slot="left-footer" v-if="showExcludedTableCheckBox">
+          <el-checkbox v-model="displayExcludedTables" 
@change="isShowExcludedTablesCols" class="exclude-checkbox" size="small">
+            <span>{{$t('excludeTableCheckbox')}}</span>
+            <el-tooltip effect="dark" placement="top" :maxHeight="200">
+              <div slot="content" class="ksd-fs-12">
+                <div>{{ $t('excludeTableCheckboxTip1') }}</div>
+                <li>• {{ $t('excludeTableCheckboxTip2') }}</li>
+                <li>• {{ $t('excludeTableCheckboxTip3') }}</li>
+                <div v-html="$t('excludeTableCheckboxTip4')"></div>
+              </div>
+              <i class="el-icon-ksd-what ksd-fs-14"></i>
             </el-tooltip>
-          </el-radio-group>
-        </div>
-        <div class="header">
-          <h4 class="ksd-left" v-if="modelInstance.model_type === 
'HYBRID'">{{$t('includeColumns')}}</h4>
-          <el-alert
-            :title="$t('tableIndexShardByTips')"
-            type="info"
-            :closable="false"
-            :show-background="false"
-            show-icon>
-          </el-alert>
-          <template v-if="modelInstance.model_type !== 'HYBRID' || 
modelInstance.model_type === 'HYBRID' && tableIndexMeta.index_range">
-            <p class="anit-table-tips" 
v-if="hasManyToManyAndAntiTable">{{$t('manyToManyAntiTableTip')}}</p>
-            <el-button plain class="ksd-ml-10" size="mini" 
@click="handleTableIndexRecognize">
-              {{$t('textRecognition')}}
-            </el-button>
-            <el-input v-model="searchColumn" size="medium" 
prefix-icon="el-ksd-icon-search_22" style="width:200px" 
:placeholder="$t('filterByColumns')"></el-input>
-          </template>
+          </el-checkbox>
+        </template>
+        <template slot="help">
+          <el-popover
+            ref="help"
+            placement="top"
+            width="366"
+            popper-class="table-index-help"
+            :title="$t('kylinLang.common.help')"
+            v-model="isShowHelp">
+            <i class="el-ksd-n-icon-close-L-outlined" @click="isShowHelp = 
false"></i>
+            <div class="sugession-blocks">
+              <p>
+                <el-tag type="info" size="mini" is-light>{{ 
$t('sugessionLabel1') }}</el-tag>
+                <span class="sugession">{{ $t('sugession1') }}</span>
+              </p>
+              <p class="ksd-mt-16">
+                <el-tag type="info" size="mini" is-light>{{ 
$t('sugessionLabel2') }}</el-tag>
+                <span class="sugession">{{ $t('sugession2') }}
+                  <span class="tips">{{ $t('tips') }}<a class="ky-a-like" 
@click="goToDataSource">{{ $t('goToDataSource') }}</a></span>
+                </span>
+              </p>
+            </div>
+            <div class="footer">
+              <span class="info ksd-mr-16">{{ $t('knowMore') }}<a 
:href="$t('shardbyManal')">{{ $t('userManual') }}</a></span>
+            </div>
+          </el-popover>
+          <el-tooltip effect="dark" :content="$t('help')" placement="top">
+            <i class="el-ksd-n-icon-help-circle-outlined" v-popover:help></i>
+          </el-tooltip>
+        </template>
+      </transfer-data>
+      <div class="ksd-mt-16" v-if="modelInstance.model_type === 'HYBRID'">
+        <div class="ksd-title-label-mini ksd-mb-8">
+          {{$t('indexTimeRange')}}
+          <span class="is-required">*</span>
         </div>
-        <div class="no-index-range" v-if="modelInstance.model_type === 
'HYBRID' && !tableIndexMeta.index_range">
-          <span>{{$t('noIndexRangeByHybrid')}}</span>
-        </div>
-        <div class="ky-simple-table" v-else>
-          <el-row class="table-header table-row ksd-mt-10">
-            <el-col :span="1"><el-checkbox v-model="isSelectAllTableIndex" 
:indeterminate="getSelectedColumns.length !== 0 && allColumns.length > 
getSelectedColumns.length" @change="selectAllTableIndex" size="small" 
/></el-col>
-            <el-col :span="14" 
class="column-name">{{$t('kylinLang.model.columnName')}}</el-col>
-            <el-col :span="3" 
class="cardinality-item">{{$t('cardinality')}}</el-col>
-            <el-col :span="3">ShardBy</el-col>
-            <el-col :span="3">{{$t('order')}}</el-col>
-          </el-row>
-          <div class="table-content table-index-layout" 
v-scroll.observe.reactive @scroll-bottom="scrollLoad">
-            <transition-group name="flip-list" tag="div">
-                <el-row v-for="(col, index) in searchAllColumns" 
:key="col.fullName" class="table-row">
-                  <el-col :span="1"><el-checkbox size="small" 
:disabled="getDisabledTableType(col)" v-model="col.isUsed" @change="(status) => 
selectTableIndex(status, col)" /></el-col>
-                  <el-col :span="14" class="column-name" 
:title="col.fullName">{{col.fullName}}<el-tooltip 
:content="$t('excludedTableIconTip')" effect="dark" placement="top"><i 
class="excluded_table-icon el-icon-ksd-exclude" v-if="isExistExcludeTable(col) 
&& displayExcludedTables"></i></el-tooltip></el-col>
-                  <el-col :span="3" class="cardinality-item">
-                    <template v-if="col.cardinality === null"><i 
class="no-data_placeholder">NULL</i></template>
-                    <template v-else>{{ col.cardinality }}</template>
-                  </el-col>
-                  <el-col :span="3" @click.native="toggleShard(col)">
-                     <i class="el-icon-success" v-if="col.isUsed" 
:class="{active: col.isShared}"></i>
-                  </el-col>
-                  <el-col :span="3" class="order-actions">
-                    <template  v-if="col.isUsed">
-                      <el-tooltip :content="$t('moveTop')" effect="dark" 
placement="top">
-                        <span :class="['icon', 'el-icon-ksd-move_to_top', 
{'is-disabled': index === 0 && !searchColumn}]" @click="topRow(col)"></span>
-                      </el-tooltip>
-                      <el-tooltip :content="$t('moveUp')" effect="dark" 
placement="top">
-                        <span :class="['icon', 'el-icon-ksd-move_up', 
{'is-disabled': index === 0}]" @click="upRow(col)"></span>
-                      </el-tooltip>
-                      <el-tooltip :content="$t('moveDown')" effect="dark" 
placement="top">
-                        <span :class="['icon', 'el-icon-ksd-move_down', 
{'is-disabled': !searchAllColumns[index + 1] || !searchAllColumns[index + 
1].isUsed}]" @click="downRow(col)"></span>
-                      </el-tooltip>
-                    </template>
-                  </el-col>
-                </el-row>
-              </transition-group>
-          </div>
-       </div>
-      </div>
-      <div slot="footer" class="dialog-footer ky-no-br-space">
-        <el-button :type="onlyBatchType ? 'primary' : ''" 
:text="onlyBatchType" @click="closeModal" 
size="medium">{{$t('kylinLang.common.cancel')}}</el-button>
-        <el-button :type="!onlyBatchType ? 'primary' : ''" 
:loading="btnLoading&&!isLoadDataLoading" size="medium" @click="submit(false)" 
:disabled="saveBtnDisable || 
btnLoading&&isLoadDataLoading">{{$t('kylinLang.common.save')}}</el-button>
-        <el-button v-if="onlyBatchType" type="primary" 
:loading="btnLoading&&isLoadDataLoading" size="medium" @click="submit(true)" 
:disabled="saveBtnDisable || 
btnLoading&&!isLoadDataLoading">{{$t('saveAndBuild')}}</el-button>
+        <el-radio-group v-model="tableIndexMeta.index_range" size="small" 
:disabled="tableIndexMeta.id !== ''">
+          <el-tooltip placement="top" :disabled="indexUpdateEnabled" 
:content="$t('refuseAddIndexTip')">
+            <el-radio :label="'HYBRID'" :disabled="!indexUpdateEnabled">
+              {{$t('kylinLang.common.HYBRID')}}<el-tooltip effect="dark" 
:content="$t('indexTimeRangeTips')" placement="top">
+                <i class="el-icon-ksd-what ksd-ml-5 ksd-fs-14"></i>
+              </el-tooltip>
+            </el-radio>
+          </el-tooltip>
+          <el-radio class="ksd-ml-16" 
:label="'BATCH'">{{$t('kylinLang.common.BATCH')}}</el-radio>
+          <el-tooltip placement="top" :disabled="indexUpdateEnabled" 
:content="$t('refuseAddIndexTip')">
+            <el-radio class="ksd-ml-16" :label="'STREAMING'" 
:disabled="!indexUpdateEnabled">{{$t('kylinLang.common.STREAMING')}}</el-radio>
+          </el-tooltip>
+        </el-radio-group>
+        <div v-if="indexRangeReqiured" class="is-required ksd-fs-12 
ksd-mt-8">{{ $t('indexRangeReqiured') }}</div>
       </div>
+    </div>
+    <div slot="footer" class="dialog-footer ky-no-br-space">
+      <el-button :type="onlyBatchType ? 'primary' : ''" :text="onlyBatchType" 
@click="closeModal" size="medium">{{$t('kylinLang.common.cancel')}}</el-button>
+      <el-button :type="!onlyBatchType ? 'primary' : ''" 
:loading="btnLoading&&!isLoadDataLoading" size="medium" @click="submit(false)" 
:disabled="saveBtnDisable || 
btnLoading&&isLoadDataLoading">{{$t('kylinLang.common.save')}}</el-button>
+      <el-button v-if="onlyBatchType" type="primary" 
:loading="btnLoading&&isLoadDataLoading" size="medium" @click="submit(true)" 
:disabled="saveBtnDisable || 
btnLoading&&!isLoadDataLoading">{{$t('saveAndBuild')}}</el-button>
+    </div>
   </el-dialog>
 </template>
 <script>
@@ -88,10 +96,10 @@
   import { Component, Watch } from 'vue-property-decorator'
   import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
   import vuex from '../../../../store'
-  import { NamedRegex } from 'config'
   import { BuildIndexStatus } from 'config/model'
-  import { handleError, handleSuccess, kylinConfirm } from 'util/business'
-  import { objectClone, changeObjectArrProperty, indexOfObjWithSomeKey, 
filterObjectArray } from 'util/index'
+  import { handleSuccess, kapConfirm, postCloudUrlMessage } from 
'util/business'
+  import { objectClone, getQueryString, indexOfObjWithSomeKey } from 
'util/index'
+  import TransferData from '../../../common/CustomTransferData/TransferData'
   import locales from './locales'
   import store, { types } from './store'
 
@@ -104,9 +112,11 @@
       ]),
       ...mapState('TableIndexEditModal', {
         isShow: state => state.isShow,
+        isHybridBatch: state => state.isHybridBatch,
         modelInstance: state => state.form.data.modelInstance,
         tableIndexDesc: state => objectClone(state.form.data.tableIndexDesc),
         indexUpdateEnabled: state => state.form.data.indexUpdateEnabled,
+        isShowHelpDefault: state => state.form.data.isShowHelp,
         callback: state => state.callback
       })
     },
@@ -125,16 +135,17 @@
         callRecognizeAggregateModal: types.CALL_MODAL
       })
     },
+    components: {
+      TransferData
+    },
     locales
   })
   export default class TableIndexEditModal extends Vue {
+    isShowHelp = false
     btnLoading = false
-    openShared = false
-    searchColumn = ''
+    allColumnList = []
     allColumns = []
-    currentPager = 1
-    pagerSize = 50
-    pager = 0
+    selectedColumns = []
     tableIndexMetaStr = JSON.stringify({
       id: '',
       col_order: [],
@@ -144,73 +155,46 @@
       index_range: ''
     })
     tableIndexMeta = JSON.parse(this.tableIndexMetaStr)
-    rules = {
-      name: [
-        {validator: this.checkName, trigger: 'blur'}
-      ]
-    }
     cloneMeta = ''
-    isSelectAllTableIndex = false
     displayExcludedTables = false
     isLoadDataLoading = false
+    alertTips = []
+    indexRangeReqiured = false
 
-    @Watch('searchColumn')
-    changeSearchColumn (val) {
-      const dom = document.querySelector('.table-index-layout .scroll-content')
-      dom && (dom.style = 'transform: translate3d(0px, 0px, 0px);')
+    get onlyBatchType () {
+      return (this.modelInstance.model_type === 'HYBRID' && 
this.tableIndexMeta.index_range !== 'STREAMING') || 
(this.modelInstance.model_type !== 'STREAMING' && this.modelInstance.model_type 
!== 'HYBRID')
     }
 
-    get getSelectedColumns () {
-      return this.allColumns.filter(it => it.isUsed)
+    get showExcludedTableCheckBox () {
+      return this.modelInstance.selected_columns.length ? 
this.modelInstance.selected_columns.filter(it => typeof it.excluded !== 
'undefined' && it.excluded).length > 0 : false
     }
 
-    get onlyBatchType () {
-      return (this.modelInstance.model_type === 'HYBRID' && 
this.tableIndexMeta.index_range !== 'STREAMING') || 
(this.modelInstance.model_type !== 'STREAMING' && this.modelInstance.model_type 
!== 'HYBRID')
+    goToDataSource () {
+      this.$router.push('/studio/source')
     }
-
-    topRow (col) {
-      let index = this.getRowIndex(col, 'fullName')
-      this.allColumns.splice(0, 0, col)
-      this.allColumns.splice(index + 1, 1)
-    }
-    upRow (col) {
-      let i = this.getRowIndex(col, 'fullName')
-      this.allColumns.splice(i - 1, 0, col)
-      this.allColumns.splice(i + 1, 1)
-    }
-    downRow (col) {
-      let i = this.getRowIndex(col, 'fullName')
-      this.allColumns.splice(i + 2, 0, col)
-      this.allColumns.splice(i, 1)
-    }
-    getRowIndex (t, key) {
-      return indexOfObjWithSomeKey(this.allColumns, key, t[key])
-    }
-    toggleShard (t) {
-      let shardStatus = t.isShared
-      changeObjectArrProperty(this.allColumns, '*', 'isShared', false)
-      t.isShared = !shardStatus
-    }
-    scrollLoad () {
-      if (this.searchAllColumns && this.searchAllColumns.length !== 
this.filterResult.length) {
-        this.currentPager += 1
-      }
+  
+    setSelectedColumns (selectedColumns) {
+      this.selectedColumns = selectedColumns
+      this.tableIndexMeta.col_order = []
+      this.selectedColumns.forEach((key) => {
+        const i = indexOfObjWithSomeKey(this.allColumns, 'key', key)
+        i !== -1 && 
this.tableIndexMeta.col_order.push(this.allColumns[i].label)
+      })
+      this.removeTips('error')
     }
-    // 是否存在多对多且被屏蔽的表
-    get hasManyToManyAndAntiTable () {
-      let flag = false
-      for (let item of this.allColumns) {
-        if (this.getDisabledTableType(item)) {
-          flag = true
-          break
-        }
-      }
-      return flag
+    setShardbyCol (label) {
+      this.tableIndexMeta.shard_by_columns = []
+      label && this.tableIndexMeta.shard_by_columns.push(label)
+      this.removeTips('error')
+    }
+    removeTips (type) {
+      const index = indexOfObjWithSomeKey(this.alertTips, 'type', type)
+      index !== -1 && this.alertTips.splice(index, 1)
     }
     // 当表为屏蔽表且表关联关系为多对多时,不能作为维度添加到索引中
     getDisabledTableType (col) {
       if (!col) return false
-      const [currentTable] = col.fullName.split('.')
+      const [currentTable] = col.column.split('.')
       const { join_tables } = this.modelInstance
       const manyToManyTables = join_tables.filter(it => it.join_relation_type 
=== 'MANY_TO_MANY').map(item => item.alias)
       return manyToManyTables.includes(currentTable) && 
this.isExistExcludeTable(col)
@@ -219,52 +203,43 @@
     isExistExcludeTable (col) {
       return typeof col.excluded !== 'undefined' ? col.excluded : false
     }
-    get filterResult () {
-      if (!this.isShow) {
-        return []
-      }
-      return this.allColumns.filter((col) => {
-        if (this.displayExcludedTables) {
-          return !this.searchColumn || 
col.fullName.toUpperCase().indexOf(this.searchColumn.toUpperCase()) >= 0
-        } else {
-          return (!this.searchColumn || 
col.fullName.toUpperCase().indexOf(this.searchColumn.toUpperCase()) >= 0) && 
!this.isExistExcludeTable(col)
+    initColumns () {
+      this.allColumns = this.modelInstance.selected_columns.filter(col => 
!this.displayExcludedTables && !this.isExistExcludeTable(col) || 
this.displayExcludedTables).map((col) => {
+        const isShardby = 
this.tableIndexMeta.shard_by_columns.indexOf(col.column) >= 0
+        const disabled = this.getDisabledTableType(col)
+        const isSelected = this.tableIndexMeta.col_order.indexOf(col.column) 
>= 0
+        if (disabled) {
+          this.alertTips = [{ text: this.$t('manyToManyAntiTableTip'), type: 
'warning' }]
         }
+        return { key: col.id, label: col.column, name: col.name, disabled: 
disabled, cardinality: col.cardinality, type: col.type, comment: col.comment, 
excluded: typeof col.excluded !== 'undefined' ? col.excluded : true, selected: 
isSelected, isShared: isShardby }
       })
+      this.selectedColumns = this.tableIndexMeta.col_order.map(item => {
+        const index = this.allColumns.findIndex(it => it.label === item)
+        return this.allColumns[index].key
+      })
+      setTimeout(() => {
+        this.isShowHelp = this.isShowHelpDefault
+      }, 200)
     }
-    get searchAllColumns () {
-      if (!this.isShow) {
-        return []
-      }
-      return this.filterResult.slice(0, this.pagerSize * this.currentPager)
-    }
-    getAllColumns () {
-      this.allColumns = []
-      let result = this.modelInstance.selected_columns.map((c) => {
-        return { fullName: c.column, cardinality: c.cardinality, excluded: 
typeof c.excluded !== 'undefined' ? c.excluded : true }
+    isShowExcludedTablesCols () {
+      this.allColumns = this.modelInstance.selected_columns.filter(col => 
!this.displayExcludedTables && !this.isExistExcludeTable(col) || 
this.displayExcludedTables).map((col) => {
+        const isShardby = 
this.tableIndexMeta.shard_by_columns.indexOf(col.column) >= 0
+        const disabled = this.getDisabledTableType(col)
+        if (disabled) {
+          this.alertTips = [{ text: this.$t('manyToManyAntiTableTip'), type: 
'warning' }]
+        }
+        return { key: col.id, label: col.column, name: col.name, disabled: 
disabled, cardinality: col.cardinality, type: col.type, comment: col.comment, 
excluded: typeof col.excluded !== 'undefined' ? col.excluded : true, selected: 
false, isShared: isShardby }
       })
-      if (this.tableIndexMeta.col_order.length) {
-        const selected = this.tableIndexMeta.col_order.map(item => {
-          const index = result.findIndex(it => it.fullName === item)
-          return {fullName: item, cardinality: result[index].cardinality, 
excluded: result[index].excluded}
-        })
-        const unSort = result.filter(item => 
!this.tableIndexMeta.col_order.includes(item.fullName))
-        result = [...selected, ...unSort]
+      if (!this.displayExcludedTables) {
+        this.removeTips('warning')
       }
-      result.forEach((ctx, index) => {
-        let obj = {fullName: ctx.fullName, cardinality: ctx.cardinality, 
excluded: ctx.excluded, isUsed: false, isShared: false, colorful: false}
-        if (this.tableIndexMeta.col_order.indexOf(ctx.fullName) >= 0) {
-          obj.isUsed = true
-        }
-        if (this.tableIndexMeta.shard_by_columns.indexOf(ctx.fullName) >= 0) {
-          obj.isShared = true
-        }
-        this.allColumns.push(obj)
+      this.selectedColumns = this.tableIndexMeta.col_order.map(item => {
+        const index = this.allColumns.findIndex(it => it.label === item)
+        return this.allColumns[index].key
       })
-      // 初始判断是否为全选状态
-      this.isSelectAllTableIndex = this.allColumns.length && 
this.allColumns.filter(it => it.isUsed).length === this.allColumns.length
     }
     get saveBtnDisable () {
-      return filterObjectArray(this.allColumns, 'isUsed', true).length === 0 
|| this.cloneMeta === JSON.stringify(this.allColumns)
+      return this.cloneMeta === JSON.stringify(this.selectedColumns)
     }
     @Watch('isShow')
     initTableIndex (val) {
@@ -278,42 +253,17 @@
           }
           Object.assign(this.tableIndexMeta, this.tableIndexDesc)
         }
-        this.getAllColumns()
-        this.cloneMeta = JSON.stringify(this.allColumns)
+        this.initColumns()
+        this.cloneMeta = JSON.stringify(this.selectedColumns)
       } else {
         this.tableIndexMeta = JSON.parse(this.tableIndexMetaStr)
       }
     }
-    pagerChange (pager) {
-      this.pager = pager
-    }
-    checkName (rule, value, callback) {
-      if (!NamedRegex.test(value)) {
-        callback(new Error(this.$t('kylinLang.common.nameFormatValidTip')))
-      } else {
-        callback()
-      }
-    }
-    clearAll () {
-      this.allColumns.forEach((col) => {
-        col.isUsed = false
-        col.isShared = false
-      })
-    }
-    changeTableIndexType () {
-      this.isSelectAllTableIndex = false
-      this.clearAll()
-    }
-    selectAll () {
-      this.allColumns.forEach((col) => {
-        col.isUsed = true
-      })
-    }
     closeModal (isSubmit) {
       this.hideModal()
       this.btnLoading = false
-      this.searchColumn = ''
-      this.isSelectAllTableIndex = false
+      this.displayExcludedTables = false
+      this.alertTips = []
       setTimeout(() => {
         this.callback && this.callback({
           isSubmit: isSubmit
@@ -360,12 +310,14 @@
         allColumns: this.allColumns,
         model: this.modelInstance
       })
-      this.allColumns.forEach((col) => {
-        if (selectedColumns.includes(col.fullName)) {
-          col.isUsed = true
+      selectedColumns.forEach((col) => {
+        const index = indexOfObjWithSomeKey(this.allColumns, 'label', col)
+        if (index !== -1) {
+          const selectedIndex = 
this.selectedColumns.indexOf(this.allColumns[index].key)
+          selectedIndex === -1 && 
this.selectedColumns.push(this.allColumns[index].key)
         }
       })
-      this.selectTableIndex()
+      this.setSelectedColumns(this.selectedColumns)
     }
     confirmSubmit (isLoadData) {
       this.isLoadDataLoading = isLoadData
@@ -388,21 +340,12 @@
       }
       let errorCb = (res) => {
         this.btnLoading = false
-        handleError(res)
+        // handleError(res)
+        this.removeTips('error')
+        this.alertTips.push({ text: res.body.msg, type: 'error' })
       }
-      // 按照sort选中列的顺序对col_order进行重新排序
-      this.tableIndexMeta.col_order = []
-      this.tableIndexMeta.shard_by_columns = []
-      this.allColumns.forEach((col) => {
-        if (col.isUsed) {
-          this.tableIndexMeta.col_order.push(col.fullName)
-        }
-        if (col.isShared) {
-          this.tableIndexMeta.shard_by_columns.push(col.fullName)
-        }
-      })
       this.tableIndexMeta.project = this.currentSelectedProject
-      this.tableIndexMeta.model_id = this.modelInstance.uuid
+      this.tableIndexMeta.model_id = this.isHybridBatch ? 
this.modelInstance.batch_id : this.modelInstance.uuid
       'name' in this.tableIndexMeta && delete this.tableIndexMeta.name
       if (this.tableIndexMeta.id) {
         this.editTableIndex({...this.tableIndexMeta, index_range: 
this.tableIndexMeta.index_range || 'EMPTY'}).then(successCb, errorCb)
@@ -411,6 +354,10 @@
       }
     }
     async submit (isLoadData) {
+      if (this.modelInstance.model_type === 'HYBRID' && 
!this.tableIndexMeta.index_range) {
+        this.indexRangeReqiured = true
+        return
+      }
       const { status } = this.tableIndexDesc || {}
       // 该字段只有在保存并构建时才会用到,纯流模型是屏蔽保存并构建的
       const isHaveBatchSegment = this.modelInstance.model_type === 'HYBRID' ? 
this.modelInstance.batch_segments.length > 0 : 
this.modelInstance.segments.length > 0
@@ -419,36 +366,20 @@
         this.tableIndexMeta.load_data = isLoadData
       }
       if (status && status !== 'EMPTY' && status === 'ONLINE') {
-        kylinConfirm(this.$t('cofirmEditTableIndex'), {cancelButtonText: 
this.$t('kylinLang.common.cancel'), confirmButtonText: 
this.$t('kylinLang.common.submit'), type: 'warning'}).then(() => {
+        kapConfirm(this.$t('cofirmEditTableIndex'), {cancelButtonText: 
this.$t('kylinLang.common.cancel'), confirmButtonText: 
this.$t('kylinLang.common.submit'), type: 'warning'}).then(() => {
           this.confirmSubmit(isLoadData)
         })
       } else {
         this.confirmSubmit(isLoadData)
       }
     }
-    selectAllTableIndex (v) {
-      this.isSelectAllTableIndex = v
-      this.allColumns.forEach(item => {
-        if (v && this.getDisabledTableType(item)) return
-        item.isUsed = v
-        if (!v) {
-          item.isShared = v
-        }
-      })
-    }
-    selectTableIndex (status, col) {
-      const selectedColumns = this.getSelectedColumns
-      const unSelected = this.allColumns.filter(it => !it.isUsed)
-      if (!status) { // 如果取消选择,isSorted重置成false
-        col.isShared = status
-      }
-      this.allColumns = [...selectedColumns, ...unSelected]
-      selectedColumns.length === this.allColumns.length && 
(this.isSelectAllTableIndex = true)
-      unSelected.length === this.allColumns.length && 
(this.isSelectAllTableIndex = false)
-    }
     // 跳转至job页面
     jumpToJobs () {
-      this.$router.push('/monitor/job')
+      if (getQueryString('from') === 'cloud' || getQueryString('from') === 
'iframe') {
+        postCloudUrlMessage(this.$route, { name: 'kapJob' })
+      } else {
+        this.$router.push('/monitor/job')
+      }
     }
   }
 </script>
@@ -457,6 +388,9 @@
   .table-edit-dialog {
     .el-dialog {
       min-width: 600px;
+      .exclude-checkbox {
+        margin: 6px 8px;
+      }
       .header {
         text-align: right;
         .el-alert--nobg {
@@ -600,4 +534,58 @@
       }
     }
   }
+  .table-index-help {
+    position: relative;
+    font-size: 12px;
+    color: @text-normal-color;
+    .el-ksd-n-icon-close-L-outlined {
+      position: absolute;
+      top: 16px;
+      right: 16px;
+      cursor: pointer;
+    }
+    .el-popover__title {
+      color: @text-normal-color;
+      font-weight: @font-medium;
+    }
+    .sugession-blocks {
+      margin-bottom: 32px;
+      p {
+        display: flex;
+        align-items: flex-start;
+        .sugession {
+          margin-left: 8px;
+          line-height: 18px;
+        }
+        .tips {
+          color: @text-disabled-color;
+          display: block;
+          margin-top: 4px;
+        }
+      }
+    }
+    .footer {
+      height: 32px;
+      line-height: 32px;
+      text-align: right;
+      background-color: @ke-background-color-secondary;
+      position: absolute;
+      bottom: 0px;
+      left: 0px;
+      width: 100%;
+      border-bottom-left-radius: 6px;
+      border-bottom-right-radius: 6px;
+      border-top: 1px solid @ke-border-secondary;
+      color: @text-placeholder-color;
+      a {
+        color: @text-normal-color;
+        &:hover {
+          color: @ke-color-primary;
+        }
+      }
+    }
+    &.el-popper[x-placement^=top] .popper__arrow::after {
+      border-top-color: @ke-background-color-secondary;
+    }
+  }
 </style>
diff --git a/kystudio/src/locale/en.js b/kystudio/src/locale/en.js
index 626cb138a9..0b860656db 100644
--- a/kystudio/src/locale/en.js
+++ b/kystudio/src/locale/en.js
@@ -181,6 +181,7 @@ exports.default = {
     readedAll: 'Readed All',
     removeAll: 'Remove All Readed',
     clearAll: 'Clear All',
+    selectAll: 'Select All',
     remove: 'Remove',
     import: 'Import',
     back: 'Back',

Reply via email to