You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by xx...@apache.org on 2023/03/20 02:32:09 UTC

[kylin] branch kylin5 updated: KYLIN-5407 KYLIN-5467 KYLIN-5468 KYLIN-5469 KYLIN-5470 KYLIN-5471 KYLIN-5476 (#2103)

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


The following commit(s) were added to refs/heads/kylin5 by this push:
     new 8c213bdab1 KYLIN-5407 KYLIN-5467 KYLIN-5468 KYLIN-5469 KYLIN-5470 KYLIN-5471 KYLIN-5476  (#2103)
8c213bdab1 is described below

commit 8c213bdab1b14d2aa7d53025405717921d4c1c78
Author: Qian Xia <la...@gmail.com>
AuthorDate: Mon Mar 20 10:32:03 2023 +0800

    KYLIN-5407 KYLIN-5467 KYLIN-5468 KYLIN-5469 KYLIN-5470 KYLIN-5471 KYLIN-5476  (#2103)
    
    * KYLIN-5467 After the batch data task list is deleted, the details page on the right is not deleted at the same time
    
    * KYLIN-5468 fix Click the "Save" button and the "Save and Build Index" icon to the right of it changes to the "in Progress" style
    
    * KYLIN-5469 Model index interface, adding filters may cause the following page-turning to be unusable
    
    * KYLIN-5470 Clear the last saved time range. The "Save" and "Save and Add index" buttons below should be grey
    
    * KYLIN-5471 Optimize the navigation bar of the model editing interface
    
    * KYLIN-5471 refine model edit layout
    
    * KYLIN-5471 limit model edit table min height
    
    * KYLIN-5471 refine model edit layout issue
    
    * KYLIN-5476 add validate tds api
    
    * KYLIN-5471 fix model edit and overview problems
    
    * KYLIN-5471 fix model edit problems
    
    * KYLIN-5471 change the er overview, change the style of grabbing
    
    * KYLIN-5471 change model navgitor styles
    
    * KYLIN-5471 change the last update time
    
    * KYLIN-5471 change the rule of show update guide
    
    * KYLIN-5471 refine model edit issues
    
    * KYLIN-5471 table default height
    
    * KYLIN-5471 show the no connected column text
---
 kystudio/src/assets/styles/main.less               |    5 +-
 .../common/GlobalDialog/dialog/detail_dialog.vue   |    6 +-
 .../common/ModelERDiagram/ModelERDiagram.vue       |  278 ++-
 .../components/common/ModelERDiagram/handler.js    |  114 +-
 .../components/common/ModelERDiagram/locales.js    |    9 +-
 .../common/ModelTools/ModelNavigationTools.vue     |  187 ++
 .../guide/modelEditPage/ActionUpdateGuide.vue      |  235 ++-
 .../components/layout/layout_left_right_top.vue    |   14 -
 kystudio/src/components/monitor/batchJobs/jobs.vue |    4 +
 .../studio/StudioModel/ModelEdit/config.js         |   44 +-
 .../studio/StudioModel/ModelEdit/index.vue         | 1914 ++++++++++----------
 .../studio/StudioModel/ModelEdit/layout.js         |  151 +-
 .../studio/StudioModel/ModelEdit/locales.js        |   10 +-
 .../studio/StudioModel/ModelEdit/model.js          |  108 +-
 .../studio/StudioModel/ModelEdit/table.js          |    1 +
 .../StudioModel/ModelList/AggregateModal/index.vue |   75 +-
 .../ModelList/Components/ModelTitleDescription.vue |  217 +++
 .../ModelList/GuideModal/GuideModal.vue            |    2 +-
 .../ModelList/ModelActions/modelActions.vue        |   55 +-
 .../StudioModel/ModelList/ModelAggregate/index.vue |   21 +-
 .../ModelAggregateView/AggAdvancedModal/index.vue  |    5 -
 .../ModelList/ModelBuildModal/build.vue            |   45 +-
 .../ModelList/ModelLayout/modelLayout.vue          |   63 +-
 .../ModelList/ModelOverview/ModelOverview.vue      |   30 +-
 .../studio/StudioModel/ModelList/index.vue         |  150 +-
 .../studio/StudioModel/ModelList/locales.js        |   14 -
 .../StudioModel/TableIndexEdit/tableindex_edit.vue |   39 +-
 kystudio/src/directive/index.js                    |   28 +-
 kystudio/src/router/index.js                       |    2 +-
 kystudio/src/service/model.js                      |    3 +
 kystudio/src/service/monitor.js                    |    6 -
 kystudio/src/store/model.js                        |    3 +
 kystudio/src/store/monitor.js                      |    3 -
 kystudio/src/store/types.js                        |    2 +-
 kystudio/src/util/dataGenerator.js                 |   15 +-
 kystudio/src/util/domHelper.js                     |    5 +-
 kystudio/src/util/plumb.js                         |   25 +-
 37 files changed, 2373 insertions(+), 1515 deletions(-)

diff --git a/kystudio/src/assets/styles/main.less b/kystudio/src/assets/styles/main.less
index 80ba809875..b51db4f0ef 100644
--- a/kystudio/src/assets/styles/main.less
+++ b/kystudio/src/assets/styles/main.less
@@ -283,10 +283,11 @@ i[class^=el-icon-]{
   color:@base-color;
 }
 .ky-move {
-  cursor:move;
+  cursor: grab;
+  cursor: move\0;
 }
 .ky-move-ing {
-  opacity: 0.8;
+  cursor: grabbing;
 }
 .ky-resize {
   i {
diff --git a/kystudio/src/components/common/GlobalDialog/dialog/detail_dialog.vue b/kystudio/src/components/common/GlobalDialog/dialog/detail_dialog.vue
index 8365247b4f..6d1b972a46 100644
--- a/kystudio/src/components/common/GlobalDialog/dialog/detail_dialog.vue
+++ b/kystudio/src/components/common/GlobalDialog/dialog/detail_dialog.vue
@@ -91,8 +91,8 @@
       <template v-else>
         <el-button v-if="needResolveCancel" :type="isSubSubmit? 'primary': ''" :text="isSubSubmit" @click="handleCloseAndResove">{{cancelT}}</el-button>
         <el-button :type="isSubSubmit? 'primary': ''" :text="isSubSubmit" v-else :class="[needResolveCancel && 'ksd-ml-12']" @click="handleClose">{{cancelT}}</el-button>
-        <el-button v-if="isSubSubmit" :loading="loading" class="ksd-ml-12" @click="handleSubmit(false)">{{submitSubText}}</el-button>
-        <el-button type="primary" v-if="!isHideSubmit" class="ksd-ml-12" :loading="loading" @click="handleSubmit(true)">{{submitT}}</el-button>
+        <el-button v-if="isSubSubmit" :loading="loading&&!isBulidLoading" :disabled="loading&&isBulidLoading" class="ksd-ml-12" @click="handleSubmit(false)">{{submitSubText}}</el-button>
+        <el-button type="primary" v-if="!isHideSubmit" class="ksd-ml-12" :loading="loading&&isBulidLoading" :disabled="loading&&!isBulidLoading" @click="handleSubmit(true)">{{submitT}}</el-button>
       </template>
     </div>
   </el-dialog>
@@ -169,6 +169,7 @@ export default class DetailDialogModal extends Vue {
   showDetail = false
   copySuccess = false
   showCopyText = false
+  isBulidLoading = false
   copyBtnClickIndex = 0
   multipleSelection = []
   dialogStatus = {
@@ -202,6 +203,7 @@ export default class DetailDialogModal extends Vue {
     this.handleReset()
   }
   handleSubmit (isSubSubmit) {
+    this.isBulidLoading = isSubSubmit
     this.loading = true
     setTimeout(() => {
       if (this.isShowSelection && this.customCallback) {
diff --git a/kystudio/src/components/common/ModelERDiagram/ModelERDiagram.vue b/kystudio/src/components/common/ModelERDiagram/ModelERDiagram.vue
index 53dccf8572..4c945b356c 100644
--- a/kystudio/src/components/common/ModelERDiagram/ModelERDiagram.vue
+++ b/kystudio/src/components/common/ModelERDiagram/ModelERDiagram.vue
@@ -1,6 +1,7 @@
 <template>
-  <div class="model-er-diagram" v-drag="{sizeChangeCb:dragBox}" v-loading="loadingER">
-    <div class="er-layout" ref="el-draw-layout" v-if="currentModel" :style="{'transform': `scale(${currentModel.canvas.zoom / 10})`}">
+  <div :class="['model-er-diagram', {'is-full-screen': isFullScreen}]" v-drag="{sizeChangeCb:dragBox}" v-loading="loadingER">
+    <el-alert class="alertChangeER" :title="$t('changeERTips')" type="warning" show-icon :closable="false" v-if="changeER && showChangeAlert"></el-alert>
+    <div class="er-layout" ref="el-draw-layout" v-if="currentModel" :style="getErLayoutStyle">
       <div :class="['table-box', {'is-lookup': t.type !== 'FACT'}]" :id="t.guid" v-for="t in currentModel.tables" :key="t.guid" :style="getTableStyles(t)">
         <div :class="['table-title', {'table-spread-out': !t.spreadOut}]" @dblclick="handleDBClick(t)">
           <span class="table-sign">
@@ -9,12 +10,9 @@
               <i v-else class="el-ksd-n-icon-dimention-table-filled kind"></i>
             </el-tooltip>
           </span>
-          <el-tooltip :content="t.alias" :visible-arrow="false" popper-class="popper--small model-alias-tooltip" effect="dark">
-            <span class="table-alias">{{t.alias}}</span>
-          </el-tooltip>
-          <!-- <el-tooltip :content="$t('tableColumnNum', {'all': getColumnNums(t), 'len': t.spreadOut ? getColumnNumInView(t) : 0})" placement="top">
-            <span class="table-column-nums"><i class="el-ksd-n-icon-eye-open-outlined ksd-mr-4 ksd-fs-16"></i>{{getColumnNumInView(t)}}</span>
-          </el-tooltip> -->
+          <span class="table-alias">
+            <span v-custom-tooltip="{text: t.alias, w: 10, effect: 'dark', 'popper-class': 'popper--small model-alias-tooltip', 'visible-arrow': false, position: 'bottom-start'}">{{t.alias}}</span>
+          </span>
           <el-tooltip :content="`${t.columns.length}`" placement="top" :disabled="typeof getColumnNums(t) === 'number'">
             <span class="table-column-nums">{{getColumnNums(t)}}</span>
           </el-tooltip>
@@ -22,7 +20,8 @@
         <div class="column-list-box">
           <ul>
             <li
-              v-for="col in t.columns"
+              class="column-li"
+              v-for="col in getColumns(t)"
               :key="col.id"
               :id="`${t.guid}_${col.name}`"
               :class="{'is-pfk': isPFK(col.name, t).isPK || isPFK(col.name, t).isFK}"
@@ -33,27 +32,25 @@
                 <span class="col-type-icon">
                   <span class="pfk-sign">{{isPFK(col.name, t).isPK ? 'PK' : isPFK(col.name, t).isFK ? 'FK' : ''}}</span><i :class="columnTypeIconMap(col.datatype)"></i>
                 </span>
-                <span :class="['col-name', {'is-link': col.isPFK}]">{{col.name}}</span>
+                <span :class="['col-name', {'is-link': col.isPFK}]" v-custom-tooltip="{text: col.name, w: 30, effect: 'dark', 'popper-class': 'popper--small', 'visible-arrow': false, position: 'bottom-start', observerId: t.guid}">{{col.name}}</span>
               </span>
             </li>
           </ul>
         </div>
       </div>
     </div>
-    <el-button-group class="diagram-actions">
-      <el-button plain icon="el-ksd-n-icon-zoom-in-outlined" @click.stop="handleZoom('up')" />
-      <el-button plain icon="el-ksd-n-icon-zoom-out-outlined" @click.stop="handleZoom('down')" />
-      <!-- <el-button plain icon="el-ksd-icon-zoom_to_default_old" @click="handleReset" /> -->
-    </el-button-group>
+    <ModelNavigationTools v-if="showShortcutsGroup && currentModel && currentModel.canvas" :showReset="changeER" :zoom="currentModel.canvas.zoom" @command="handleActionsCommand" @addZoom="handleZoom('up')" @reduceZoom="handleZoom('down')" @autoLayout="autoLayout" @reset="resetERDiagram" />
   </div>
 </template>
 <script>
 import Vue from 'vue'
 import { Component } from 'vue-property-decorator'
-import { handleSuccessAsync } from 'util'
+import { handleSuccessAsync, objectClone } from 'util'
 import { columnTypeIcon } from '../../../config'
-import { mapActions, mapGetters, mapState } from 'vuex'
-import { initPlumb, drawLines, customCanvasPosition } from './handler'
+import { mapActions, mapGetters, mapState, mapMutations } from 'vuex'
+import { initPlumb, drawLines, customCanvasPosition, createAndUpdateSvgGroup } from './handler'
+import { modelRenderConfig } from '../../studio/StudioModel/ModelEdit/config'
+import ModelNavigationTools from '../ModelTools/ModelNavigationTools'
 import locales from './locales'
 @Component({
   props: {
@@ -62,6 +59,18 @@ import locales from './locales'
       default () {
         return {}
       }
+    },
+    showShortcutsGroup: {
+      type: Boolean,
+      default: true
+    },
+    showChangeAlert: {
+      type: Boolean,
+      default: true
+    },
+    source: {
+      type: String,
+      default: 'overview'
     }
   },
   computed: {
@@ -69,15 +78,22 @@ import locales from './locales'
       modelList: state => state.model.modelsList
     }),
     ...mapGetters([
-      'currentProjectData'
+      'currentProjectData',
+      'isFullScreen'
     ])
   },
   methods: {
     ...mapActions({
       loadColumnOfModel: 'LOAD_DATASOURCE_OF_MODEL'
+    }),
+    ...mapMutations({
+      toggleFullScreen: 'TOGGLE_SCREEN'
     })
   },
-  locales
+  locales,
+  components: {
+    ModelNavigationTools
+  }
 })
 export default class ModelERDiagram extends Vue {
   currentModel = null
@@ -86,9 +102,17 @@ export default class ModelERDiagram extends Vue {
   plumbInstance = null
   loadingER = false
   defaultZoom = 9
+  defaultCanvasBackup = null
+  defaultTableBackup = null
   foreignKeys = []
   primaryKeys = []
   linkFocus = []
+  showOnlyConnectedColumn = false
+  changeER = false
+
+  get getErLayoutStyle () {
+    return {'transform': `scale(${(this.currentModel?.canvas?.zoom ?? 9) / 10})`}
+  }
   // 判断是否为主外键
   isPFK (column, table) {
     return {
@@ -108,7 +132,11 @@ export default class ModelERDiagram extends Vue {
     const num = Math.floor((height - 38) / 34)
     return table.spreadOut ? table.columns.length > num ? `${num}` :`${table.columns.length}` : ''
   }
+  getColumns (t) {
+    return this.showOnlyConnectedColumn ? t.columns.filter(it => this.isPFK(it.name, t).isPK || this.isPFK(it.name, t).isFK) : t.columns
+  }
   async created () {
+    this.loadingER = true
     await this.getTableColumns()
     this.$nextTick(() => {
       const { plumbInstance, plumbTool } = initPlumb(this.$el.querySelector('.er-layout'), this.currentModel.canvas ?? this.defaultZoom)
@@ -117,12 +145,19 @@ export default class ModelERDiagram extends Vue {
       drawLines(this, this.plumbTool, this.currentModel.joints)
       this.handleSortTables()
       if (!this.currentModel.canvas) {
-        const cv = customCanvasPosition(this.$el.querySelector('.er-layout'), this.currentModel, this.defaultZoom)
+        const cv = customCanvasPosition(this, this.querySelector('.er-layout'), this.currentModel, this.defaultZoom)
         this.$set(this.currentModel, 'canvas', cv)
         this.$nextTick(() => {
           this.plumbTool.refreshPlumbInstance()
+          createAndUpdateSvgGroup(null, { type: 'update' })
         })
       }
+      this.loadingER = false
+      this.exchangeTableData()
+      this.defaultCanvasBackup = objectClone(this.currentModel.canvas)
+      this.defaultTableBackup = objectClone(this.currentModel.tables)
+      this.exchangePosition()
+
     })
   }
   // 获取 table 位置信息
@@ -140,16 +175,15 @@ export default class ModelERDiagram extends Vue {
     this.$set(this.currentModel.canvas.coordinate[`${t.alias}`], 'height', boxH)
     this.$nextTick(() => {
       this.plumbTool.refreshPlumbInstance()
+      createAndUpdateSvgGroup(null, { type: 'update' })
     })
   }
   getTableColumns () {
-    this.loadingER = true
     return new Promise((resolve, reject) => {
       const { name } = this.currentProjectData
       const [{ canvas }] = this.modelList.filter(item => item.alias === this.model.name)
       this.loadColumnOfModel({project: name, model_name: this.model.name}).then(async (result) => {
         const values = await handleSuccessAsync(result)
-        this.loadingER = false
         this.currentModel = {
           ...this.model,
           tables: this.model.tables.map(table => {
@@ -183,15 +217,27 @@ export default class ModelERDiagram extends Vue {
     const mL = drawBoard.offsetLeft ?? 0
     const mT = drawBoard.offsetTop ?? 0
     drawBoard.style.cssText += `margin-left: ${mL + x}px; margin-top: ${mT + y}px`
+    this.changeER = true
   }
   // 放大缩小
   handleZoom (type) {
     let {zoom} = this.currentModel.canvas
     if (type === 'up') {
-      this.$set(this.currentModel.canvas, 'zoom', zoom + zoom * 0.2)
+      this.$set(this.currentModel.canvas, 'zoom', zoom + 1 > 10 ? 10 : zoom + 1)
     } else {
-      this.$set(this.currentModel.canvas, 'zoom', zoom + zoom * -0.2)
+      this.$set(this.currentModel.canvas, 'zoom', zoom - 1 < 4 ? 4 : zoom - 1)
     }
+    this.changeER = true
+  }
+  // 自动布局
+  autoLayout () {
+    const cv = customCanvasPosition(this, this.$el.querySelector('.er-layout'), this.currentModel, this.currentModel.canvas.zoom)
+    this.$set(this.currentModel, 'canvas', cv)
+    this.changeER = true
+    this.$nextTick(() => {
+      this.plumbTool.refreshPlumbInstance()
+      createAndUpdateSvgGroup(null, { type: 'update' })
+    })
   }
   handleMouseEnterColumn (e, t, col) {
     const linkList  = []
@@ -199,9 +245,11 @@ export default class ModelERDiagram extends Vue {
       if (item.guid === t.guid) {
         linkList.push(...item.joins.filter(it => it.primaryKey === `${t.alias}.${col.name}`).map(it => ({table_guid: it.guid, linked_column: it.foreignKey})))
       } else if (item.joins.filter(it => it.guid === t.guid).length > 0) {
-        const [linkJoin] = item.joins.filter(it => it.guid === t.guid && it.foreignKey === `${t.alias}.${col.name}`)
-        if (!linkJoin) return
-        linkList.push({table_guid: item.guid, linked_column: linkJoin.primaryKey})
+        const linkJoin = item.joins.filter(it => it.guid === t.guid && it.foreignKey === `${t.alias}.${col.name}`)
+        if (!linkJoin.length) return
+        linkJoin.forEach(it => {
+          linkList.push({table_guid: item.guid, linked_column: it.primaryKey})
+        })
       }
     })
     linkList.forEach(lk => {
@@ -242,6 +290,88 @@ export default class ModelERDiagram extends Vue {
       })
     }
   }
+  // 额外功能
+  handleActionsCommand (command, showOnlyConnectedColumn) {
+    this.changeER = true
+    this.showOnlyConnectedColumn = showOnlyConnectedColumn
+    if (command === 'collapseAllTables') {
+      const tableTitleHeight = document.querySelector('.table-title').offsetHeight
+      this.currentModel.tables.forEach((item) => {
+        this.$set(item, 'spreadOut', false)
+        this.$set(this.currentModel.canvas.coordinate[`${item.alias}`], 'height', tableTitleHeight + 4)
+      })
+    } else if (command === 'expandAllTables') {
+      this.currentModel.tables.forEach((item) => {
+        this.$set(item, 'spreadOut', true)
+        this.$set(this.currentModel.canvas.coordinate[`${item.alias}`], 'height', item.spreadHeight)
+      })
+    } else if (command === 'showOnlyConnectedColumn') {
+      this.currentModel.tables.forEach(item => {
+        const { columns } = item
+        const len = columns.filter(it => this.isPFK(it.name, item).isFK || this.isPFK(it.name, item).isPK).length
+        const columnHeight = document.querySelector('.column-li').offsetHeight
+        const tableTitleHeight = document.querySelector('.table-title').offsetHeight
+        const sumHeight = columnHeight * len
+        if (sumHeight !== 0) {
+          this.$set(item, 'spreadOut', true)
+          this.$set(this.currentModel.canvas.coordinate[`${item.alias}`], 'height', tableTitleHeight + sumHeight + 5)
+        } else {
+          this.$set(item, 'spreadOut', false)
+          this.$set(this.currentModel.canvas.coordinate[`${item.alias}`], 'height', tableTitleHeight + 4)
+        }
+      })
+    }
+    this.$nextTick(() => {
+      this.plumbTool.refreshPlumbInstance()
+      createAndUpdateSvgGroup(null, { type: 'update' })
+    })
+  }
+  exchangeTableData () {
+    const currentTableTitle = this.$el.querySelector('.table-title')
+    const modelTableBoxBorder = +window.getComputedStyle(currentTableTitle)['borderWidth'].replace(/px/, '')
+    for (let item in this.currentModel.tables) {
+      const t = this.currentModel.tables[item]
+      const canvasHeight = this.currentModel.canvas.coordinate[`${t.alias}`].height
+      if (canvasHeight === currentTableTitle.offsetHeight + modelTableBoxBorder * 2 + 4) {
+        this.$set(t, 'spreadOut', false)
+        this.$set(t, 'spreadHeight', modelRenderConfig.tableBoxHeight)
+      } else {
+        this.$set(t, 'spreadHeight', canvasHeight || modelRenderConfig.tableBoxHeight)
+      }
+    }
+  }
+  // 重置 ER 图
+  resetERDiagram () {
+    this.changeER = false
+    this.showOnlyConnectedColumn = false
+    this.currentModel.canvas.zoom = 9
+    if (this.defaultTableBackup) {
+      this.currentModel.tables.forEach((item, index) => {
+        this.$set(item, 'spreadOut', this.defaultTableBackup[index].spreadOut)
+        this.$set(item, 'spreadHeight', this.defaultTableBackup[index].spreadHeight)
+      })
+    }
+    this.toggleFullScreen(false)
+    this.$set(this.currentModel, 'canvas', objectClone(this.defaultCanvasBackup))
+    const drawBoard = this.$el.querySelector('.er-layout')
+    drawBoard.style.cssText += `margin-left: 0; margin-top: 0`
+    this.$nextTick(() => {
+      this.plumbTool.refreshPlumbInstance()
+      createAndUpdateSvgGroup(null, { type: 'update' })
+    })
+  }
+  fullScreen () {
+    this.changeER = true
+    this.toggleFullScreen(!this.isFullScreen)
+  }
+  exchangePosition () {
+    if (this.source === 'modelList') {
+      if (!(this.currentModel && this.currentModel.tables)) return
+      const [factTable] = this.currentModel.tables.filter(it => it.type === 'FACT')
+      const factGuid = factTable.guid
+      document.getElementById(factGuid).scrollIntoView()
+    }
+  }
 }
 </script>
 <style lang="less">
@@ -249,6 +379,17 @@ export default class ModelERDiagram extends Vue {
 .model-er-diagram {
   width: 100%;
   height: 100%;
+  cursor: grab;
+  .alertChangeER {
+    z-index: 10;
+  }
+  &.is-full-screen {
+    position: fixed;
+    top: 0;
+    left: 0;
+    background: @ke-background-color-secondary;
+    z-index: 10;
+  }
   .er-layout {
     position: absolute;
     top: 0;
@@ -256,12 +397,6 @@ export default class ModelERDiagram extends Vue {
     width: 100%;
     height: 100%;
     user-select: none;
-    .jtk-connector.jtk-hover {
-      path {
-        stroke: #A5B2C5;
-        stroke-width: 1;
-      }
-    }
     .jtk-connector.is-focus {
       path {
         stroke: @ke-color-primary;
@@ -269,14 +404,19 @@ export default class ModelERDiagram extends Vue {
       }
     }
     .jtk-overlay {
-      background: @fff;
+      background: @ke-background-color-secondary;
       .join-type {
-        color: #A5B2C5;
+        color: @ke-border-secondary-active;
         font-size: 30px;
-        cursor: move;
-        // &:hover {
-        //   color: #0875DA;
-        // }
+        cursor: grab;
+      }
+      &.jtk-hover {
+        .join-type {
+          color: #9DCEFB;
+          &:hover {
+            color: @ke-color-primary;
+          }
+        }
       }
       .close-icon {
         display: none;
@@ -294,13 +434,13 @@ export default class ModelERDiagram extends Vue {
     background-color: @fff;
     position: absolute;
     border-radius: 6px;
-    border: 2px solid #E6EBF4;
+    border: 2px solid @ke-color-secondary-hover;
     box-sizing: border-box;
     display: flex;
     flex-direction: column;
     z-index: 10;
     &.is-focus {
-      border: 2px solid #0867BF;
+      border: 2px solid @ke-color-primary-hover;
     }
     &.is-broken.is-focus {
       border: 2px solid @ke-color-danger;
@@ -309,7 +449,6 @@ export default class ModelERDiagram extends Vue {
       border: 2px solid #9DCEFB;
     }
     &:hover {
-      // box-shadow: @fact-hover-shadow;
       border: 2px solid #9DCEFB;
       .scrollbar-track-y{
         opacity: 1;
@@ -328,10 +467,9 @@ export default class ModelERDiagram extends Vue {
       position: initial;
       margin-top: 32px;
     }
-    // overflow: hidden;
     .table-title {
       background-color: @base-color;
-      color:#fff;
+      color: @fff;
       line-height: 38px;
       border-radius: 5px 5px 0 0;
       height: 38px; // 高度改变需要更改 getColumnNumInView 方法里相应值
@@ -344,20 +482,19 @@ export default class ModelERDiagram extends Vue {
       .table-sign {
         display: inline-block;
         font-size: 0;
-        cursor: pointer;
+        cursor: default;
       }
       .table-column-nums {
         font-size: 12px;
-        cursor: pointer;
+        cursor: default;
         i {
           margin-top: -2px;
         }
       }
       .table-alias {
-        text-overflow: ellipsis;
-        overflow: hidden;
         line-height: 29px\0;
         width: calc(~"100% - 50px");
+        height: 100%;
         display: inline-block;
         margin-left: 4px;
         font-weight: bold;
@@ -372,12 +509,10 @@ export default class ModelERDiagram extends Vue {
       }
       i {
         color:@fff;
-        // margin: auto 6px 8px;
       }
     }
     .column-list-box {
       overflow: hidden;
-      border-top: solid 1px @line-border-color;
       flex: 1;
       ul {
         li {       
@@ -389,30 +524,32 @@ export default class ModelERDiagram extends Vue {
           font-size: 14px;
           position: relative;
           &.is-hover {
-            background-color: #CEE6FD;
-            border: 2px solid #0875DA;
+            background-color: @base-color-8;
+            border: 2px solid @ke-color-primary;
           }
           &.is-pfk {
             .col-name {
-              color: #0875DA;
+              color: @ke-color-primary;
             }
             background-color: #F0F8FE;
             &:hover {
-              background: #CEE6FD;
-              border: 2px solid #0875DA
+              background: @base-color-8;
+              border: 2px solid @ke-color-primary
             }
           }
-          // &:hover{
-          //   border-top: 2px solid #9DCEFB;
-          //   border-bottom: 2px solid #9DCEFB;
-          //   background-color: #CEE6FD;
-          // }
           .ksd-nobr-text {
             width: calc(~'100% - 25px');
+            display: flex;
+            align-items: center;
+            .tip_box {
+              height: 32px;
+              line-height: 32px;
+            }
           }
           .col-type-icon {
             color: @text-disabled-color;
             font-size: 12px;
+            margin-right: 5px;
             .pfk-sign{
               color: @text-placeholder-color;
               position: absolute;
@@ -422,7 +559,7 @@ export default class ModelERDiagram extends Vue {
           .col-name {
             color: @text-title-color;
             &.is-link {
-              color: #0875DA;
+              color: @ke-color-primary;
               font-weight: @font-medium;
             }
           }
@@ -441,6 +578,23 @@ export default class ModelERDiagram extends Vue {
     right: 20px;
     z-index: 50;
   }
+  .shortcuts-group {
+    position: absolute;
+    right: 20px;
+    bottom: 20px;
+  }
+}
+#use-group:hover {
+  > path:not(#use) {
+    stroke: #9DCEFB;
+    stroke-width: 2;
+  }
+}
+.model-action-tools {
+  margin-left: 50px;
+  .is-active {
+    color: @ke-color-primary;
+  }
 }
 .model-alias-tooltip {
   margin-top: -10px !important;
diff --git a/kystudio/src/components/common/ModelERDiagram/handler.js b/kystudio/src/components/common/ModelERDiagram/handler.js
index 4c99b2e211..85c14c28df 100644
--- a/kystudio/src/components/common/ModelERDiagram/handler.js
+++ b/kystudio/src/components/common/ModelERDiagram/handler.js
@@ -17,17 +17,29 @@ export function initPlumb (renderDom, zoom) {
 export function drawLines (that, plumbTool, joints) {
   plumbTool.lazyRender(() => {
     joints.forEach(v => {
+      const [firstJoin] =  v.joins.slice(0, 1)
+      const primaryKey = []
+      const foreignKey = []
       v.joins.forEach(item => {
+        primaryKey.push(item.primaryKey.split('.')[1])
+        foreignKey.push(item.foreignKey.split('.')[1])
         that.primaryKeys.push(item.primaryKey)
         that.foreignKeys.push(item.foreignKey)
-        addPlumbPoints(plumbTool, v.guid)
-        addPlumbPoints(plumbTool, item.guid)
-        const conn = plumbTool.connect(v.guid, item.guid, () => {}, {
-          joinType: v.type ?? '',
-          brokenLine: false
-        })
-        allConnectList[`${v.guid}$${item.guid}`] = conn
       })
+      addPlumbPoints(plumbTool, v.guid)
+      addPlumbPoints(plumbTool, firstJoin.guid)
+      const conn = plumbTool.connect(v.guid, firstJoin.guid, () => {}, {
+        joinType: v.type ?? '',
+        brokenLine: false,
+        cancelBubble: false
+      })
+      const labelLayout = conn.getOverlay(v.guid + (firstJoin.guid + 'label'))
+      const labelCanvas = labelLayout.canvas
+      const lineCanvas = conn.canvas
+
+      allConnectList[`${v.guid}$${firstJoin.guid}`] = conn
+      handleHoverLinks(labelCanvas, {fKeys: foreignKey, pKeys: primaryKey, fid: firstJoin.guid, pid: v.guid})
+      createAndUpdateSvgGroup(lineCanvas, {type: 'create', isBroken: false, fKeys: foreignKey, pKeys: primaryKey, fid: firstJoin.guid, pid: v.guid})
     })
   })
 }
@@ -45,13 +57,17 @@ function addPlumbPoints (plumbTool, guid) {
 }
 
 // 根据树形结构自定义表的位置
-export function customCanvasPosition (renderDom, model, zoom) {
-  const { tables } = model
+export function customCanvasPosition (vm, renderDom, model, zoom) {
+  const { tables, canvas: currentCanvas } = model
   const canvas = {
     coordinate: {},
     zoom: zoom
   }
-  const layers = autoCalcLayer(tables)
+  const layersRoot = autoCalcLayer(tables, model)
+  let layers = layersRoot?.rightNodes?.db
+  if (layersRoot?.leftNodes) {
+    layers = [...layers, ...layersRoot.leftNodes.db]
+  }
   if (layers && layers.length > 0) {
     const baseL = modelRenderConfig.baseLeft
     const baseT = modelRenderConfig.baseTop
@@ -59,12 +75,15 @@ export function customCanvasPosition (renderDom, model, zoom) {
     const centerL = renderDomBound.width / 2 - modelRenderConfig.tableBoxWidth / 2
     const moveL = layers[0].X - centerL
     for (let k = 0; k < layers.length; k++) {
+      const currentT = layers[k].tree.tables[layers[k].guid]
+      const centerT = renderDomBound.height / 3 - (currentT?.drawSize?.height ?? modelRenderConfig.tableBoxHeight) / 2
+      const moveT = layers[0].Y - centerT
       let [currentTable] = tables.filter(item => item.guid === layers[k].guid)
       canvas.coordinate[`${currentTable.alias}`] = {
         x: baseL - moveL + layers[k].X,
-        y: baseT + layers[k].Y,
+        y: baseT - moveT + layers[k].Y,
         width: modelRenderConfig.tableBoxWidth,
-        height: modelRenderConfig.tableBoxHeight
+        height: currentCanvas?.coordinate?.[`${currentTable.alias}`]?.height ?? modelRenderConfig.tableBoxHeight
       }
     }
   }
@@ -72,13 +91,78 @@ export function customCanvasPosition (renderDom, model, zoom) {
 }
 
 // 获取树形结构
-function autoCalcLayer (tables) {
+function autoCalcLayer (tables, model) {
+  const { canvas: currentCanvas } = model
+  const defaultSize = {
+    x: 0,
+    y: 0,
+    height: 230,
+    width: 200
+  }
   const [factTable] = tables.filter(it => it.type === 'FACT')
   if (!factTable) {
     return
   }
+  const tbs = {}
+  tables.forEach(it => {
+    tbs[it.guid] = {
+      ...it,
+      drawSize: {...(currentCanvas?.coordinate[`${it.alias}`] ?? defaultSize)}
+    }
+  })
   const rootGuid = factTable.guid
-  const tree = new ModelTree({rootGuid: rootGuid, showLinkCons: allConnectList})
+  const tree = new ModelTree({rootGuid: rootGuid, showLinkCons: allConnectList, tables: tbs})
   tree.positionTree()
-  return tree.nodeDB.db
+  return tree.nodeDB.rootNode
+}
+
+// 自定义扩大 line svg hover 热区
+export function createAndUpdateSvgGroup (lineCanvas, conn, guid) {
+  if (conn.type === 'create') {
+    const sign = 'http://www.w3.org/2000/svg'
+    const path = lineCanvas.firstChild
+    if (!path) return
+    if (lineCanvas.querySelector('g')) return
+    const newPath = path.cloneNode(true)
+    const group = document.createElementNS(sign, 'g')
+    const d = path.getAttribute('d')
+
+    group.id = conn.isBroken ? 'broken-use-group' : 'use-group'
+    newPath.setAttribute('d', d)
+    newPath.setAttribute('stroke-width', 10)
+    newPath.setAttribute('stroke', 'transparent')
+    newPath.setAttribute('id', 'use')
+    group.appendChild(path)
+    group.appendChild(newPath)
+    lineCanvas.appendChild(group)
+
+    handleHoverLinks(group, conn)
+  } else {
+    const lineGroups = !guid ? Object.keys(allConnectList): Object.keys(allConnectList).filter(it => it.split('$').includes(guid))
+    lineGroups.forEach(item => {
+      const line = allConnectList[item].canvas
+      if (line.querySelector('g')) {
+        const paths = line.querySelectorAll('path')
+        const firstPathLine = paths[0].getAttribute('d')
+        paths[1].setAttribute('d', firstPathLine)
+      } else {
+        // createAndUpdateSvgGroup(line, allConnectList[item].isBroken, 'create')
+      }
+    })
+  }
+}
+
+function handleHoverLinks (element, conn) {
+  element.onmouseenter = function () {
+    document.getElementById(`${conn.fid}`).className += ' link-hover'
+    document.getElementById(`${conn.pid}`).className += ' link-hover'
+    conn.fKeys.forEach(item => document.getElementById(`${conn.fid}_${item}`).className += ' is-hover')
+    conn.pKeys.forEach(item => document.getElementById(`${conn.pid}_${item}`).className += ' is-hover')
+  }
+  element.onmouseleave = function () {
+    document.getElementById(`${conn.fid}`).classList.remove('link-hover')
+    document.getElementById(`${conn.pid}`).classList.remove('link-hover')
+    conn.fKeys.forEach(item => document.getElementById(`${conn.fid}_${item}`).classList.remove('is-hover'))
+    conn.pKeys.forEach(item => document.getElementById(`${conn.pid}_${item}`).classList.remove('is-hover'))
+  }
 }
diff --git a/kystudio/src/components/common/ModelERDiagram/locales.js b/kystudio/src/components/common/ModelERDiagram/locales.js
index 0271837f26..f652051923 100644
--- a/kystudio/src/components/common/ModelERDiagram/locales.js
+++ b/kystudio/src/components/common/ModelERDiagram/locales.js
@@ -2,6 +2,13 @@ export default {
   en: {
     FACT: 'Fact Table',
     LOOKUP: 'Dimension Table',
-    tableColumnNum: ''
+    tableColumnNum: '',
+    fullScreen: 'Full Screen',
+    cancelFullScreen: 'Exit Full Screen',
+    autoLayout: 'Auto-organize',
+    expandAllTables: 'Expand All Tables',
+    collapseAllTables: 'Collapse all tables',
+    showOnlyConnectedColumn: 'Show only connected columns',
+    changeERTips: 'The current adjustment only affects the preview view, and the default snapshot will be restored when viewed again. If you want to edit it, please go to the edit view to do so.'
   }
 }
diff --git a/kystudio/src/components/common/ModelTools/ModelNavigationTools.vue b/kystudio/src/components/common/ModelTools/ModelNavigationTools.vue
new file mode 100644
index 0000000000..110e0dc422
--- /dev/null
+++ b/kystudio/src/components/common/ModelTools/ModelNavigationTools.vue
@@ -0,0 +1,187 @@
+<template>
+    <div class="shortcuts-group">
+      <el-tooltip :content="$t('reset')" placement="top" v-if="showReset">
+        <div class="resetER el-ksd-n-icon-resure-outlined" @click="resetER"></div>
+      </el-tooltip>
+      <el-tooltip :content="isFullScreen ? $t('cancelFullScreen') : $t('fullScreen')" placement="top" >
+        <div :class="['full-screen', !isFullScreen ? 'el-ksd-n-icon-arrows-alt-outlined' : 'el-ksd-n-icon-shrink-exit-outlined']" @click="fullScreen"></div>
+      </el-tooltip>
+      <el-tooltip :content="$t('autoLayout')" placement="top" >
+        <div class="auto-layout el-ksd-n-icon-map-outlined" @click="autoLayout"></div>
+      </el-tooltip>
+      <span class="divide-line">|</span>
+      <el-dropdown class="action-dropdown" @command="(command) => handleActionsCommand(command)">
+        <el-button icon="el-ksd-n-icon-column-3-outlined" iconr="el-ksd-n-icon-arrow-table-down-filled" nobg-text></el-button>
+        <el-dropdown-menu slot="dropdown" class="model-action-tools">
+          <el-dropdown-item command="expandAllTables" :class="{'is-active': activeToolItem === 'expandAllTables'}"><i class="el-ksd-n-icon-column-3-outlined ksd-mr-8"></i>{{$t('expandAllTables')}}</el-dropdown-item>
+          <el-dropdown-item command="collapseAllTables" :class="{'is-active': activeToolItem === 'collapseAllTables'}"><i class="el-ksd-n-icon-workflow-outlined ksd-mr-8"></i>{{$t('collapseAllTables')}}</el-dropdown-item>
+          <el-dropdown-item command="showOnlyConnectedColumn" :class="{'is-active': activeToolItem === 'showOnlyConnectedColumn'}"><i class="el-ksd-n-icon-workspace-outlined ksd-mr-8"></i>{{$t('showOnlyConnectedColumn')}}</el-dropdown-item>
+        </el-dropdown-menu>
+      </el-dropdown>
+      <span class="divide-line">|</span>
+      <div class="zoom-tools"><span class="reduce-zoom el-ksd-n-icon-minus-outlined" @click="reduceZoom"></span><span class="zoom-num">{{zoom / 10 * 100}}%</span><span class="add-zoom el-ksd-n-icon-plus-outlined" @click="addZoom"></span></div>
+    </div>
+  </template>
+  
+  <script>
+  import Vue from 'vue'
+  import { Component } from 'vue-property-decorator'
+  import { mapGetters, mapMutations } from 'vuex'
+  @Component({
+    props: {
+      zoom: Number,
+      showReset: {
+        type: Boolean,
+        default: false
+      }
+    },
+    computed: {
+      ...mapGetters([
+        'isFullScreen'
+      ])
+    },
+    methods: {
+      ...mapMutations({
+        toggleFullScreen: 'TOGGLE_SCREEN'
+      })
+    },
+    locales: {
+      'en': {
+        reset: 'Reset',
+        fullScreen: 'Full Screen',
+        cancelFullScreen: 'Exit Full Screen',
+        autoLayout: 'Auto-organize',
+        expandAllTables: 'Expand All Tables',
+        collapseAllTables: 'Collapse all tables',
+        showOnlyConnectedColumn: 'Show only connected columns'
+      },
+      'zh-cn': {
+        reset: '重置',
+        fullScreen: '全屏',
+        cancelFullScreen: '退出全屏',
+        autoLayout: '自动整理',
+        expandAllTables: '展开全部表',
+        collapseAllTables: '收起全部表',
+        showOnlyConnectedColumn: '仅显示连接列'
+      }
+    }
+  })
+  export default class ModelNavigationTools extends Vue {
+    activeToolItem = ''
+    collapseAllTables = false
+    showOnlyConnectedColumn = false
+    resetER () {
+      this.activeToolItem = ''
+      this.collapseAllTables = false
+      this.showOnlyConnectedColumn = false
+      this.$emit('reset')
+    }
+    // 放大视图
+    addZoom () {
+      this.$emit('addZoom')
+    }
+    // 缩小视图
+    reduceZoom () {
+      this.$emit('reduceZoom')
+    }
+    // 全屏
+    fullScreen () {
+      this.toggleFullScreen(!this.isFullScreen)
+    }
+    // 自动布局
+    autoLayout () {
+      this.$emit('autoLayout')
+    }
+    handleActionsCommand (command) {
+      this.activeToolItem = command
+      switch (command) {
+        case 'collapseAllTables':
+          this.collapseAllTables = true
+          this.showOnlyConnectedColumn = false
+          break
+        case 'expandAllTables':
+          this.collapseAllTables = false
+          this.showOnlyConnectedColumn = false
+          break
+        case 'showOnlyConnectedColumn':
+          this.collapseAllTables = false
+          this.showOnlyConnectedColumn = true
+      }
+      this.$emit('command', command, this.showOnlyConnectedColumn)
+    }
+  }
+  </script>
+  
+  <style lang="less">
+  @import '../../../assets/styles/variables.less';
+  .shortcuts-group {
+    // top: 70%;
+    background-color: @fff;
+    border: 1px solid @ke-border-secondary;
+    box-shadow: 6px 6px 14px rgba(0, 0, 0, 0.04);
+    border-radius: 6px;
+    padding: 7px;
+    box-sizing: border-box;
+    display: flex;
+    // flex-direction: column;
+    align-items: center;
+    color: @text-normal-color;
+    > div {
+      margin-right: 8px;
+      &:last-child {
+        margin-right: 0;
+      }
+    }
+    .resetER {
+      position: absolute;
+      left: -25px;
+      top: 10px\0;
+      cursor: pointer;
+    }
+    .zoom-tools {
+      display: flex;
+      // flex-direction: column;
+      align-items: center;
+      .reduce-zoom {
+        cursor: pointer;
+        font-size: 16px;
+      }
+      .zoom-num {
+        margin-left: 8px;
+        margin-right: 8px;
+      }
+      .add-zoom {
+        cursor: pointer;
+        font-size: 16px;
+      }
+    }
+    .divide-line {
+      border: 0;
+      // border-left: 1px solid #E6EBF4;
+      // margin: 4px 0 20px 0;
+      color: @ke-border-secondary;
+      width: 1px;
+      height: 70%;
+      margin-right: 8px;
+    }
+    .full-screen {
+      font-size: 16px;
+      cursor: pointer;
+    }
+    .auto-layout {
+      font-size: 16px;
+      cursor: pointer;
+    }
+    .action-dropdown {
+      margin-top: -3px;
+      .el-ksd-n-icon-column-3-outlined {
+        font-size: 16px;
+        color: @text-normal-color;
+      }
+      .el-ksd-n-icon-arrow-table-down-filled {
+        font-size: 12px;
+        color: @text-normal-color;
+      }
+    }
+  }
+  </style>
diff --git a/kystudio/src/components/guide/modelEditPage/ActionUpdateGuide.vue b/kystudio/src/components/guide/modelEditPage/ActionUpdateGuide.vue
index b2ceb148cf..8cb78fc17d 100644
--- a/kystudio/src/components/guide/modelEditPage/ActionUpdateGuide.vue
+++ b/kystudio/src/components/guide/modelEditPage/ActionUpdateGuide.vue
@@ -1,31 +1,67 @@
 <template>
-  <el-dialog
-    append-to-body
-    class="guide-layout"
-    :visible="true"
-    width="600px"
-    status-icon="el-ksd-n-icon-info-color-filled"
-    :close-on-click-modal="false"
-    :before-close="closeGuide">
-    <span slot="title" class="guide-title">{{$t('actionGuideTitle')}}</span>
-    <div class="step guide-step1" v-if="step === 1">
-      <p class="content">{{$t('step1Msg')}}</p>
-      <img src="../../../assets/img/guide/model_edit_step1.jpg" width="200px" alt="guid_1">
+  <div class="update-model-actions-mask" @mousedown.stop>
+    <div class="guide-step1" v-show="step === 1">
+      <div :class="['model-edit-title', {'has-global-alert': hasGlobalAlert}]"></div>
+      <div :class="['title-tip', {'has-global-alert': hasGlobalAlert}]">
+        <div class="title">
+          <i class="info-icon el-ksd-n-icon-info-circle-filled"></i>
+          <span>{{$t('actionGuideTitle')}}</span>
+          <i class="close el-ksd-n-icon-close-outlined" @click="closeGuide"></i>
+        </div>
+        <p class="content">{{$t('step1Msg')}}</p>
+        <div class="footer">
+          <span class="step-num">{{`${step}/5`}}</span>
+          <el-button type="primary" @click.stop="nextStep">{{$t('kylinLang.common.next')}}</el-button>
+        </div>
+      </div>
     </div>
-    <div class="step guide-step2" v-if="step === 2">
-      <p class="content">{{$t('step2Msg')}}</p>
-      <img src="../../../assets/img/guide/model_edit_step2.png" width="396px" alt="guid_2">
+    <div class="guide-step5" v-show="step === 2">
+      <div class="model-actions"></div>
+      <div class="title-tip">
+        <div class="title">
+          <i class="info-icon el-ksd-n-icon-info-circle-filled"></i>
+          <span>{{$t('actionGuideTitle')}}</span>
+          <i class="close el-ksd-n-icon-close-outlined" @click="closeGuide"></i>
+        </div>
+        <p class="content">{{$t('step2Msg')}}</p>
+        <div class="footer">
+          <span class="step-num">{{`${step}/5`}}</span>
+          <div>
+            <el-button @click.stop="prevStep" v-if="step > 1">{{$t('kylinLang.common.back')}}</el-button>
+            <el-button type="primary" @click.stop="nextStep">{{$t('kylinLang.common.next')}}</el-button>
+          </div>
+        </div>
+      </div>
     </div>
-    <div class="step guide-step3" v-if="step === 3">
-      <p class="content">{{$t('step3Msg')}}</p>
-      <img src="../../../assets/img/guide/model_edit_step3.jpg" width="450px" alt="guid_3">
-    </div>
-    <span slot="footer" class="guide-footer">
-      <span class="step-num">{{`${step}/3`}}</span>
-      <el-button @click.stop="--step" v-if="step > 1">{{$t('kylinLang.common.back')}}</el-button>
-      <el-button type="primary" @click.stop="nextStep">{{$t('kylinLang.common.next')}}</el-button>
-    </span>
-  </el-dialog>
+    <el-dialog
+      append-to-body
+      class="guide-layout"
+      :visible="showDialog"
+      width="600px"
+      :modal="false"
+      status-icon="el-ksd-n-icon-info-color-filled"
+      :close-on-click-modal="false"
+      :before-close="closeGuide">
+      <span slot="title" class="guide-title">{{$t('actionGuideTitle')}}</span>
+      <div class="step guide-step2" v-if="step === 3">
+        <p class="content">{{$t('step3Msg')}}</p>
+        <img src="../../../assets/img/guide/model_edit_step1.jpg" width="200px" alt="guid_1">
+      </div>
+      <div class="step guide-step3" v-if="step === 4">
+        <p class="content">{{$t('step4Msg')}}</p>
+        <img src="../../../assets/img/guide/model_edit_step3.jpg" width="396px" alt="guid_2">
+      </div>
+      <div class="step guide-step4" v-if="step === 5">
+        <p class="content">{{$t('step5Msg')}}</p>
+        <img src="../../../assets/img/guide/model_edit_step2.png" width="450px" alt="guid_3">
+      </div>
+      <span slot="footer" class="guide-footer">
+        <span class="step-num">{{`${step}/5`}}</span>
+        <el-button @click.stop="prevStep" v-if="step > 1">{{$t('kylinLang.common.back')}}</el-button>
+        <el-button type="primary" @click.stop="nextStep">{{$t('kylinLang.common.next')}}</el-button>
+      </span>
+    </el-dialog>
+  </div>
 </template>
 
 <script>
@@ -34,28 +70,57 @@ import { Component } from 'vue-property-decorator'
 @Component({
   locales: {
     'en': {
-      actionGuideTitle: 'Welcome to our new table for modeling canvas!',
-      step1Msg: 'You can have an overview of the number of columns included in each table, and which column is used as the primary key (PK) and foreign key (FK).',
-      step2Msg: 'You can expand or collapse a table by selecting the respective option on the menu, or double-clicking on the header.',
-      step3Msg: 'All columns used for relation are pinned at the top. By hovering on the column, the associated tables and columns will be highlighted.'
-    },
-    'zh-cn': {
-      actionGuideTitle: '欢迎使用我们建模画布的新表!',
-      step1Msg: '我们优化了表的信息布局,现在,你可以更清晰的了解列数量和主外键(FK/PK)信息。',
-      step2Msg: '我们增加了展开/收起功能,现在,你可以通过双击表头或下拉菜单快速将表展开收起。',
-      step3Msg: '我们优化了列的关联显示,现在,你可以在置顶看到所有关联列,通过将鼠标悬浮,有关联关系的表及列将被高亮显示。'
+      actionGuideTitle: 'Welcome to our new modeling canvas!',
+      step1Msg: 'You can now get a quick overview of the model\'s basic information, search content, and operations here.',
+      step2Msg: 'Here you can control the size, the full screen, and the view of the canvas.',
+      step3Msg: 'You can have an overview of the number of columns included in each table, and which column is used as the primary key (PK) and foreign key (FK).',
+      step4Msg: 'You can expand or collapse a table by selecting the respective option on the menu, or double-clicking on the header.',
+      step5Msg: 'All columns used for relation are pinned at the top. By hovering on the column, the associated tables and columns will be highlighted.'
     }
   }
 })
 export default class ActionUpdateGuide extends Vue {
-  step = 1
+  step = 0
+  showDialog = false
+  get hasGlobalAlert () {
+    const alterDom = document.getElementsByClassName('alter-block')
+    return alterDom.length > 0
+  }
+  mounted () {
+    setTimeout(() => {
+      this.step = 1
+      const modelTitleDom = document.getElementsByClassName('model-edit-header')[0]
+      const modelActionDom = document.getElementsByClassName('shortcuts-group')[0]
+      const cloneModelTitle = modelTitleDom.cloneNode(true)
+      const cloneModelActionDom = modelActionDom.cloneNode(true)
+      const dom = this.$el.querySelector('.model-edit-title')
+      const guideActions = this.$el.querySelector('.model-actions')
+      dom.appendChild(cloneModelTitle)
+      guideActions.appendChild(cloneModelActionDom)
+    }, 500)
+  }
+  // 下一步
   nextStep () {
-    if (this.step < 3) {
+    if (this.step < 5) {
       ++this.step
+      if (this.step > 2) {
+        this.showDialog = true
+      } else {
+        this.showDialog = false
+      }
     } else {
       this.closeGuide()
     }
   }
+  // 上一步
+  prevStep () {
+    this.step -= 1
+    if (this.step > 2) {
+      this.showDialog = true
+    } else {
+      this.showDialog = false
+    }
+  }
   closeGuide () {
     this.$emit('close-guide')
   }
@@ -63,6 +128,102 @@ export default class ActionUpdateGuide extends Vue {
 </script>
 
 <style lang="less">
+@import '../../../assets/styles/variables.less';
+.update-model-actions-mask {
+  z-index: 2000;
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, .8);
+  cursor: default;
+  .model-edit-title {
+    background: #fff;
+    top: 50px;
+    right: 0;
+    position: absolute;
+    width: calc(~'100% - 180px');
+    box-sizing: border-box;
+    pointer-events: none;
+    &.has-global-alert {
+      top: 90px;
+    }
+  }
+  .title-tip.has-global-alert {
+    top: 200px;
+  }
+  .model-actions {
+    pointer-events: none;
+  }
+  .title-tip {
+    width: 366px;
+    height: 156px;
+    border-radius: 12px;
+    background: @fff;
+    position: absolute;
+    top: 160px;
+    left: 200px;
+    padding: 0 17px 17px 17px;
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
+    &::after {
+      content: '';
+      border-top: 0;
+      border-left: 15px solid transparent;
+      border-bottom: 15px solid #fff;
+      border-right: 15px solid transparent;
+      position: absolute;
+      top: -14px;
+      left: 50%;
+      transform: translate(-50%, 0);
+    }
+    .title {
+      font-size: 12px;
+      font-weight: 500;
+      height: 40px;
+      line-height: 40px;
+      .info-icon {
+        color: #0875DA;
+        font-size: 14px;
+      }
+      .close {
+        position: absolute;
+        top: 13px;
+        right: 17px;
+        font-size: 14px;
+        cursor: pointer;
+      }
+    }
+    .content {
+      flex: 1;
+      font-size: 14px;
+    }
+    .footer {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      .step-num {
+        color: #8B99AE;
+        font-size: 14px;
+      }
+    }
+  }
+  .guide-step5 {
+    .title-tip {
+      bottom: 100px;
+      right: 0;
+      top: auto;
+      left: auto;
+      &::after {
+        top: auto;
+        bottom: -14px;
+        transform: rotate(180deg) translate(10px, 0);
+      }
+    }
+  }
+}
 .guide-layout {
   .guide-title {
     vertical-align: middle;
diff --git a/kystudio/src/components/layout/layout_left_right_top.vue b/kystudio/src/components/layout/layout_left_right_top.vue
index 9165d03baa..63dbeff82d 100644
--- a/kystudio/src/components/layout/layout_left_right_top.vue
+++ b/kystudio/src/components/layout/layout_left_right_top.vue
@@ -88,20 +88,6 @@
             </el-alert>
           </div>
           <div :class="['grid-content', 'bg-purple-light']" id="scrollContent">
-            <!-- <el-col :span="24" v-show="gloalProjectSelectShow" class="bread-box"> -->
-              <!-- 面包屑在dashboard页面不显示 -->
-              <!-- <el-breadcrumb separator="/" class="ksd-ml-30">
-                <el-breadcrumb-item>
-                  <span>{{$t('kylinLang.menu.' + currentRouterNameArr[0])}}</span>
-                </el-breadcrumb-item>
-                <el-breadcrumb-item v-if="currentRouterNameArr[1]" :to="{ path: '/' + currentRouterNameArr[0] + '/' + currentRouterNameArr[1]}">
-                  <span>{{$t('kylinLang.menu.' + currentRouterNameArr[1])}}</span>
-                </el-breadcrumb-item>
-                <el-breadcrumb-item v-if="currentRouterNameArr[2]" >
-                  {{currentRouterNameArr[2]}}
-                </el-breadcrumb-item>
-              </el-breadcrumb> -->
-            <!-- </el-col> -->
             <el-col :span="24" class="main-content">
               <transition :name="isAnimation ? 'slide' : null" v-bind:css="isAnimation">
                 <router-view v-on:addProject="addProject" v-if="isShowRouterView"></router-view>
diff --git a/kystudio/src/components/monitor/batchJobs/jobs.vue b/kystudio/src/components/monitor/batchJobs/jobs.vue
index 0d8b08ecfb..64fda35ed3 100644
--- a/kystudio/src/components/monitor/batchJobs/jobs.vue
+++ b/kystudio/src/components/monitor/batchJobs/jobs.vue
@@ -1192,6 +1192,10 @@ export default class JobsList extends Vue {
           this.filter.status = []
         }
       }
+      if (this.selectedJob.id && jobIds.indexOf(this.selectedJob.id) !== -1) { // 展示的详情job 被删除了
+        this.selectedJob = {}
+        this.showStep = false
+      }
       this.manualRefreshJobs()
       this.$message({
         type: 'success',
diff --git a/kystudio/src/components/studio/StudioModel/ModelEdit/config.js b/kystudio/src/components/studio/StudioModel/ModelEdit/config.js
index 4645e3c595..0cb6d4da08 100644
--- a/kystudio/src/components/studio/StudioModel/ModelEdit/config.js
+++ b/kystudio/src/components/studio/StudioModel/ModelEdit/config.js
@@ -2,28 +2,32 @@ let baseIndex = 100
 let rootBox = '.model-edit-outer'
 export const modelRenderConfig = {
   jsPlumbAnchor: [
-    [ 0.2, 0, 0, -1 ],
-    [ 0.4, 0, 0, -1 ],
-    [ 0.6, 0, 0, -1 ],
-    [ 0.8, 0, 0, -1 ],
-    [ 1, 0.2, 1, 0 ],
-    [ 1, 0.4, 1, 0 ],
-    [ 1, 0.6, 1, 0 ],
-    [ 1, 0.8, 1, 0 ],
-    [ 0, 0.2, -1, 0 ],
-    [ 0, 0.4, -1, 0 ],
-    [ 0, 0.6, -1, 0 ],
-    [ 0, 0.8, -1, 0 ],
-    [ 0.8, 1, 0, 1 ],
-    [ 0.6, 1, 0, 1 ],
-    [0.4, 1, 0, 1],
-    [0.2, 1, 0, 1]
+    [ 0.1, 0, 0, -1 ],
+    [ 0.3, 0, 0, -1 ],
+    [ 0.5, 0, 0, -1 ],
+    [ 0.7, 0, 0, -1 ],
+    [ 0.9, 0, 0, -1 ],
+    [ 1, 0.1, 1, 0 ],
+    [ 1, 0.3, 1, 0 ],
+    [ 1, 0.5, 1, 0 ],
+    [ 1, 0.7, 1, 0 ],
+    [ 1, 0.9, 1, 0 ],
+    [ 0, 0.1, -1, 0 ],
+    [ 0, 0.3, -1, 0 ],
+    [ 0, 0.5, -1, 0 ],
+    [ 0, 0.7, -1, 0 ],
+    [ 0, 0.9, -1, 0 ],
+    [ 0.9, 1, 0, 1 ],
+    [ 0.7, 1, 0, 1 ],
+    [ 0.5, 1, 0, 1 ],
+    [0.3, 1, 0, 1],
+    [0.1, 1, 0, 1]
   ], // 连线动态附着点设置
-  baseLeft: 100, // 可视区域距离画布最左侧距离
-  baseTop: 10, // 可视区域距离画布最顶部距离
+  baseLeft: 0, // 可视区域距离画布最左侧距离
+  baseTop: 100, // 可视区域距离画布最顶部距离
   tableBoxWidth: 200, // table盒子宽度
   tableBoxHeight: 230, // table盒子高度
-  tableBoxLeft: 50, // table盒子相对于左侧兄弟元素距离
+  tableBoxLeft: 100, // table盒子相对于左侧兄弟元素距离
   tableBoxTop: 50, // table盒子相对于顶部兄弟元素距离
   zoom: 9,
   marginClient: {
@@ -116,7 +120,7 @@ export const modelRenderConfig = {
         box: rootBox
       },
       datasource: {
-        top: 52,
+        top: 82,
         left: 10,
         width: 250,
         height: 420,
diff --git a/kystudio/src/components/studio/StudioModel/ModelEdit/index.vue b/kystudio/src/components/studio/StudioModel/ModelEdit/index.vue
index 19e6433679..af21fb6ecb 100644
--- a/kystudio/src/components/studio/StudioModel/ModelEdit/index.vue
+++ b/kystudio/src/components/studio/StudioModel/ModelEdit/index.vue
@@ -1,471 +1,460 @@
 <template>
-  <div class="model-edit-outer" @drop='dropTable($event)' @dragover='allowDrop($event)' v-drag="{sizeChangeCb:dragBox}" @dragleave="dragLeave" @click="removeTableFocus">
-    <el-alert class="lose-fact-table-alert" v-if="modelRender && modelRender.tables && Object.keys(modelRender.tables).length && !showFactTableAlert" :title="$t('loseFactTableAlert')" type="warning" show-icon :closable="false"></el-alert>
-    <div class="model-edit">
-      <el-button v-visible @click="guideActions"></el-button>
-      <!-- 为了保证 gif 每次都从第一帧开始播放,所以每次都要重新加载 -->
-      <kylin-empty-data class="gifEmptyData" :content="$t('noTableTip')" :image="require('../../../../assets/img/editmodel.gif') + '?v=' + new Date().getTime()" v-if="!Object.keys(modelRender.tables).length"></kylin-empty-data>
-      <!-- table box -->
-      <div
-        :ref="`table_${t.guid}`"
-        class="table-box"
-        @click="(e) => activeTablePanel(e, t)"
-        @mouseleave="removeCustomHoverStatus"
-        :id="t.guid"
-        v-event-stop
-        :class="['js_' + t.alias.toLocaleLowerCase(), {'isLookup':t.kind==='LOOKUP'}]"
-        v-for="t in modelRender && modelRender.tables || []"
-        :key="`${t.guid}`"
-        :style="tableBoxStyle(t.drawSize)"
-      >
-        <div :class="['table-title', {'table-spread-out': !t.spreadOut}]" :data-zoom="modelRender.zoom" v-drag:change.left.top="t.drawSize" @dblclick="handleDBClick(t)">
-          <span class="table-sign">
-            <el-tooltip :content="$t(t.kind)" placement="top">
-              <i class="el-ksd-n-icon-symbol-f-filled kind" v-if="t.kind === 'FACT'"></i>
-              <i v-else class="el-ksd-n-icon-dimention-table-filled kind"></i>
-            </el-tooltip>
-          </span>
-          <el-tooltip :visible-arrow="false" popper-class="popper--small model-alias-tooltip" effect="dark" class="name" :content="t.alias">
-            <span class="alias-span">{{t.alias}}</span>
-          </el-tooltip>
-          <el-tooltip :content="`${t.columns.length}`" placement="top" :disabled="typeof getColumnNums(t) === 'number'">
-            <span class="table-column-nums">{{getColumnNums(t)}}</span>
-          </el-tooltip>
-          <el-dropdown class="table-dropdown" trigger="click" :disabled="isSchemaBrokenModel">
-            <span class="setting-icon" v-if="!isSchemaBrokenModel"><i class="el-ksd-n-icon-more-vertical-filled"></i></span>
-            <el-dropdown-menu class="table-actions" slot="dropdown">
-              <el-dropdown-item>
-                <div v-if="t.kind === 'FACT' || modelInstance.checkTableCanSwitchFact(t.guid)">
-                  <div class="action switch" :class="{'disabled': t.source_type === 1}" v-if="t.kind === 'FACT'" @click.stop="changeTableType(t)">
-                    <span>{{$t('switchLookup')}}</span>
+  <div class="model-edit-layout">
+    <div class="model-edit-header">
+      <div class="model-title">
+        <model-title-description :modelData="modelData" source="modelEdit" v-if="modelData" hideTimeTooltip />
+      </div>
+      <div class="model-search-layout">
+        <el-input
+          @input="searchModelEverything"
+          clearable
+          :placeholder="$t('kylinLang.common.search')"
+          v-model="modelGlobalSearch"
+          prefix-icon="el-ksd-n-icon-search-outlined"
+        ></el-input>
+        <transition name="bounceleft">
+          <div class="search-board" v-if="modelGlobalSearch && showSearchResult">
+            <el-row>
+              <el-col :span="16" class="search-content-col">
+                <div v-scroll.reactive class="search-result-box" v-keyborad-select="{scope:'.search-content', searchKey: modelGlobalSearch}" v-search-highlight="{scope:'.search-name', hightlight: modelGlobalSearch}">
+                  <div>
+                    <div class="search-group" v-for="(k,v) in searchResultData" :key="v">
+                      <ul>
+                        <li class="search-content" v-for="(x, i) in k" @click="(e) => {selectResult(e, x)}" :key="x.action + x.name + i"><span class="search-category">[{{$t(x.i18n)}}]</span> <span class="search-name">{{x.name}}</span><span v-html="x.extraInfo"></span></li>
+                      </ul>
+                      <div class="ky-line"></div>
+                    </div>
+                    <div v-show="Object.keys(searchResultData).length === 0" class="search-noresult">{{$t('kylinLang.common.noResults')}}</div>
                   </div>
-                  <div class="action switch" v-if="modelInstance.checkTableCanSwitchFact(t.guid)" @click.stop="changeTableType(t)">
-                    <span >{{$t('switchFact')}}</span>
+                </div>
+                </el-col>
+              <el-col :span="8" class="search-history">
+                <div class="search-action-list" v-if="modelSearchActionHistoryList && modelSearchActionHistoryList.length">
+                  <div class="action-list-title">{{$t('searchHistory')}}</div>
+                  <div class="action-content" v-for="(item, index) in modelSearchActionHistoryList" :key="index">
+                    <div class="action-title">
+                      <i :class="item.icon" class="ksd-mr-6 search-list-icon"></i>
+                      <div class="action-desc" v-html="item.title"></div>
+                    </div>
+                    <div class="action-detail"></div>
                   </div>
                 </div>
-              </el-dropdown-item>
-              <el-dropdown-item>
-                <div class="spread-or-expand-table" @click="handleDBClick(t)"><span>{{$t(t.spreadOut ? 'spreadTableColumns' : 'expandTableColumns')}}</span><span class="db">{{$t('doubleClick')}}</span></div>
-              </el-dropdown-item>
-              <el-dropdown-item v-if="t.kind !== 'FACT'">
-                <div @click.stop="openEditAliasForm(t)">{{$t('editTableAlias')}}</div>
-              </el-dropdown-item>
-              <el-dropdown-item>
-                <div class="action del" @click.stop="delTable(t)"> {{$t('kylinLang.common.delete')}}</div>
-              </el-dropdown-item>
-            </el-dropdown-menu>
-          </el-dropdown>
+              </el-col>
+            </el-row>
+            <div class="search-footer"><span><el-tag type="beta" size="mini">Beta</el-tag>{{$t('betaSearchTips')}}<a class="feedback-btn" target="_blank" :href="$store.state.system.serverAboutKap['ke.license.isEvaluation'] ? supportUrl : $t('introductionUrl')">{{$t('feedback')}}</a></span></div>
+          </div>
+        </transition>
+      </div>
+      <div class="model-actions">
+        <div class="btn-group ky-no-br-space">
+          <el-button @click="goModelList" size="medium">{{$t('kylinLang.common.cancel')}}</el-button>
+          <el-button size="medium" type="primary" @click="saveModelEvent" :loading="saveBtnLoading">{{$t('kylinLang.common.save')}}</el-button>
         </div>
-        <div class="column-search-box" v-show="t.spreadOut"><el-input prefix-icon="el-ksd-icon-search_22" v-model="t.filterColumnChar" @input="t.filterColumns()" size="small" :placeholder="$t('kylinLang.common.search')"></el-input></div>
-        <div class="column-list-box ksd-drag-box" :ref="`${t.guid}_column_box`" v-if="!t.drawSize.isOutOfView && t.showColumns.length" v-scroll.observe.reactive @scroll-bottom="handleScrollBottom(t)">
-          <!-- 锚点拖拽功能暂时隐藏
-            @mouseleave="(e) => handleMouseLeave(e, t, col)"
-            @mouseenter="(e) => handleMouseEnterColumn(e, t, col)"
-          -->
-          <ul>
-            <li class="column-search-results" v-if="t.filterColumnChar && t._cache_search_columns.length > 0"><span>{{$t('searchResults', {number: t._cache_search_columns.length})}}</span></li>
-            <li
-              :id="`${t.guid}_${col.column}`"
-              @dragover="(e) => {dragColumnEnter(e, t, col)}"
-              @dragleave="dragColumnLeave"
-              @dragstart="(e) => {dragColumns(e, col, t)}"
-              @mouseenter="(e) => handleMouseEnterColumn(e, t, col)"
-              @mouseleave="(e) => handleMouseLeave(e, t, col)"
-              draggable
-              class="column-li"
-              :class="{'column-li-cc': col.is_computed_column, 'is-link': col.isPK || col.isFK}"
-              @drop.stop='(e) => {dropColumn(e, col, t)}'
-              v-for="col in t.showColumns"
-              :key="col.name"
-            >
-              <span class="ksd-nobr-text">
-                <span class="col-type-icon">
-                  <span class="is-pfk" v-show="col.isPK || col.isFK">{{`${col.isFK && col.isPK ? 'FK PK' : col.isFK ? 'FK' : 'PK'}`}}</span><i :class="columnTypeIconMap(col.datatype)"></i>
-                </span>
-                <el-tooltip :visible-arrow="false" popper-class="popper--small" effect="dark" :content="col.name" placement="bottom-start">
-                  <span :class="['col-name']">{{col.name}}</span>
-                </el-tooltip>
-              </span>
-            </li>
-            <!-- 
-              渲染可计算列 
-              @mouseleave="(e) => handleMouseLeave(e, t, col)"
-              @mouseenter="(e) => handleMouseEnterColumn(e, t, col)"
-            -->
-            <template v-if="t.kind=== 'FACT'">
-              <li :class="['column-li', 'column-li-cc', {'is-link': col.isPK || col.isFK}]"
-                @drop='(e) => {dropColumn(e, {name: col.columnName }, t)}'
+      </div>
+    </div>
+    <div class="model-edit-outer" @drop='dropTable($event)' @dragover='allowDrop($event)' v-drag="{sizeChangeCb:dragBox}" @dragleave="dragLeave" @click="removeTableFocus">
+      <el-alert class="lose-fact-table-alert" v-if="modelRender && modelRender.tables && Object.keys(modelRender.tables).length && !showFactTableAlert" :title="$t('loseFactTableAlert')" type="warning" show-icon :closable="false"></el-alert>
+      <div class="model-edit" :style="getModelEditStyles">
+        <!-- 为了保证 gif 每次都从第一帧开始播放,所以每次都要重新加载 -->
+        <kylin-empty-data class="gifEmptyData" :content="$t('noTableTip')" :image="require('../../../../assets/img/editmodel.gif') + '?v=' + new Date().getTime()" v-if="!Object.keys(modelRender.tables).length"></kylin-empty-data>
+        <!-- table box -->
+        <div
+          :ref="`table_${t.guid}`"
+          class="table-box"
+          @click="(e) => activeTablePanel(e, t)"
+          @mouseleave="removeCustomHoverStatus"
+          :id="t.guid"
+          v-event-stop
+          :class="['js_' + t.alias.toLocaleLowerCase(), {'isLookup':t.kind==='LOOKUP'}]"
+          v-for="t in modelRender && modelRender.tables || []"
+          :key="`${t.guid}`"
+          :style="tableBoxStyle(t.drawSize)"
+        >
+          <div :class="['table-title', {'table-spread-out': !t.spreadOut}]" :data-zoom="modelRender.zoom" v-drag:change.left.top="t.drawSize" @dblclick="handleDBClick(t)">
+            <span class="table-sign">
+              <el-tooltip :content="$t(t.kind)" placement="top">
+                <i class="el-ksd-n-icon-symbol-f-filled kind" v-if="t.kind === 'FACT'"></i>
+                <i v-else class="el-ksd-n-icon-dimention-table-filled kind"></i>
+              </el-tooltip>
+            </span>
+            <span class="alias-span name">
+              <span v-custom-tooltip="{text: t.alias, w: 10, effect: 'dark', 'popper-class': 'popper--small model-alias-tooltip', 'visible-arrow': false, position: 'bottom-start', observerId: t.guid}">{{t.alias}}</span>
+            </span>
+            <el-tooltip :content="`${t.columns.length}`" placement="top" :disabled="typeof getColumnNums(t) === 'number'">
+              <span class="table-column-nums">{{getColumnNums(t)}}</span>
+            </el-tooltip>
+            <el-dropdown ref="tableActionsDropdown" class="table-dropdown" trigger="click" :disabled="isSchemaBrokenModel">
+              <span class="setting-icon" @click.stop v-if="!isSchemaBrokenModel"><i class="el-ksd-n-icon-more-vertical-filled"></i></span>
+              <el-dropdown-menu class="table-actions-dropdown" slot="dropdown">
+                <el-dropdown-item>
+                  <div v-if="t.kind === 'FACT' || modelInstance.checkTableCanSwitchFact(t.guid)">
+                    <div class="action switch" :class="{'disabled': t.source_type === 1}" v-if="t.kind === 'FACT'" @click.stop="changeTableType(t)">
+                      <span>{{$t('switchLookup')}}</span>
+                    </div>
+                    <div class="action switch" v-if="modelInstance.checkTableCanSwitchFact(t.guid)" @click.stop="changeTableType(t)">
+                      <span >{{$t('switchFact')}}</span>
+                    </div>
+                  </div>
+                </el-dropdown-item>
+                <el-dropdown-item>
+                  <div class="spread-or-expand-table" @click="handleDBClick(t)"><span>{{$t(t.spreadOut ? 'spreadTableColumns' : 'expandTableColumns')}}</span><span class="db">{{$t('doubleClick')}}</span></div>
+                </el-dropdown-item>
+                <el-dropdown-item v-if="t.kind !== 'FACT'">
+                  <div @click.stop="openEditAliasForm(t)">{{$t('editTableAlias')}}</div>
+                </el-dropdown-item>
+                <el-dropdown-item>
+                  <div class="action del" @click.stop="delTable(t)"> {{$t('kylinLang.common.delete')}}</div>
+                </el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+          </div>
+          <div class="column-search-box" v-show="t.spreadOut && !showOnlyConnectedColumn" @click.stop><el-input prefix-icon="el-ksd-icon-search_22" v-model="t.filterColumnChar" @input="t.filterColumns()" size="small" :placeholder="$t('kylinLang.common.search')"></el-input></div>
+          <div class="column-list-box ksd-drag-box" :ref="`${t.guid}_column_box`" v-if="!t.drawSize.isOutOfView && t.showColumns.length" v-scroll.observe.reactive @scroll-bottom="handleScrollBottom(t)">
+            <ul>
+              <li class="column-search-results" v-if="t.filterColumnChar && t._cache_search_columns.length > 0"><span>{{$t('searchResults', {number: t._cache_search_columns.length})}}</span></li>
+              <li
+                :id="`${t.guid}_${col.column}`"
                 @dragover="(e) => {dragColumnEnter(e, t, col)}"
                 @dragleave="dragColumnLeave"
                 @dragstart="(e) => {dragColumns(e, col, t)}"
-                @mouseleave="(e) => handleMouseLeave(e, t, col)"
                 @mouseenter="(e) => handleMouseEnterColumn(e, t, col)"
+                @mouseleave="(e) => handleMouseLeave(e, t, col)"
                 draggable
-                v-for="col in modelRender.computed_columns"
+                class="column-li"
+                :class="{'column-li-cc': col.is_computed_column, 'is-link': col.isPK || col.isFK}"
+                @drop.stop='(e) => {dropColumn(e, col, t)}'
+                v-for="col in getCurrentColumns(t)"
                 :key="col.name"
               >
                 <span class="ksd-nobr-text">
                   <span class="col-type-icon">
                     <span class="is-pfk" v-show="col.isPK || col.isFK">{{`${col.isFK && col.isPK ? 'FK PK' : col.isFK ? 'FK' : 'PK'}`}}</span><i :class="columnTypeIconMap(col.datatype)"></i>
                   </span>
-                  <el-tooltip :visible-arrow="false" popper-class="popper--small" effect="dark" :content="col.columnName" placement="bottom-start">
-                    <span :class="['col-name']">{{col.columnName}}</span>
-                  </el-tooltip>
+                  <span :class="['col-name']" v-custom-tooltip="{text: col.name, w: 30, effect: 'dark', 'popper-class': 'popper--small', 'visible-arrow': false, position: 'bottom-start', observerId: t.guid}">{{col.name}}</span>
                 </span>
               </li>
-            </template>
-            <li class="li-load-more" v-if="t.hasMoreColumns && t.hasScrollEnd"><i class="el-ksd-icon-loading_16"></i></li>
-          </ul>
-        </div>
-        <!-- <kylin-nodata v-else :content="$t('noResults')"></kylin-nodata> -->
-        <!-- 拖动操纵 -->
-        <DragBar :dragData="t.drawSize" :dragZoom="modelRender.zoom"/>
-        <!-- 拖动操纵 -->
-      </div>
-      <!-- table box end -->
-    </div>
-    <!-- datasource面板  index 3-->
-    <div class="tool-icon icon-ds" v-if="panelAppear.datasource.icon_display" :class="{active: panelAppear.datasource.display}" v-event-stop @click="toggleMenu('datasource')"><i class="el-icon-ksd-data_source"></i></div>
-    <transition name="bounceleft">
-      <div class="panel-box panel-datasource"  v-show="panelAppear.datasource.display" :style="panelStyle('datasource')" v-event-stop>
-        <div class="panel-title" v-drag:change.left.top="panelAppear.datasource"><span class="title">{{$t('kylinLang.common.dataSource')}}</span><span class="close" @click="toggleMenu('datasource')"><i class="el-icon-ksd-close"></i></span></div>
-        <!-- <div v-scroll style="height:calc(100% - 79px)"> -->
-        <DataSourceBar
-          :ignore-node-types="['column']"
-          class="tree-box"
-          :class="{'iframeTreeBox': $store.state.config.platform === 'iframe'}"
-          ref="datasourceTree"
-          :project-name="currentSelectedProject"
-          :is-show-load-source="true"
-          :is-show-load-table="datasourceActions.includes('loadSource')"
-          :is-show-load-table-inner-btn="datasourceActions.includes('loadSource')"
-          :is-show-settings="false"
-          :is-show-action-group="false"
-          :is-expand-on-click-node="false"
-          :expand-node-types="['datasource', 'database']"
-          :draggable-node-types="['table']"
-          :searchable-node-types="['table']"
-          :is-model-have-fact="modelInstance && !!modelInstance.fact_table"
-          :is-second-storage-enabled="modelInstance && modelInstance.second_storage_enabled"
-          @drag="dragTable">
-        </DataSourceBar>
-        <!-- </div> -->
-        <!-- 拖动操纵 -->
-        <DragBar :dragData="panelAppear.datasource"/>
-        <!-- 拖动操纵 -->
-      </div>
-    </transition>
-    <!-- datasource面板  end-->
-    <div class="tool-icon-group" v-event-stop>
-      <div class="tool-icon broken-icon" v-if="panelAppear.brokenFocus.icon_display" @click="focusBrokenLinkedTable">
-        <i class="el-icon-ksd-broken_disconnect"></i>
-      </div>
-      <div class="tool-icon" v-if="panelAppear.dimension.icon_display" :class="{active: panelAppear.dimension.display}" @click="toggleMenu('dimension')">D</div>
-      <div class="tool-icon" v-if="panelAppear.measure.icon_display" :class="{active: panelAppear.measure.display}" @click="toggleMenu('measure')">M</div>
-      <div class="tool-icon" v-if="panelAppear.cc.icon_display" :class="{active: panelAppear.cc.display}" @click="toggleMenu('cc')"><i class="el-icon-ksd-computed_column"></i></div>
-      <div class="tool-icon" v-if="panelAppear.search.icon_display" :class="{active: panelAppear.search.display}" @click="toggleMenu('search')">
-        <i class="el-icon-ksd-search"></i>
-        <span class="new-icon">New</span>
-      </div>
-    </div>
-    <!-- 快捷操作 -->
-    <div class="sub-tool-icon-group" v-event-stop>
-      <div class="tool-icon" @click="reduceZoom"><i class="el-icon-ksd-shrink" ></i></div>
-      <div class="tool-icon" @click="addZoom"><i class="el-icon-ksd-enlarge"></i></div>
-      <!-- <div class="tool-icon" v-event-stop>{{modelRender.zoom}}0%</div> -->
-      <div class="tool-icon tool-full-screen" @click="fullScreen"><i class="el-icon-ksd-full_screen_2" v-if="!isFullScreen"></i><i class="el-icon-ksd-collapse_2" v-if="isFullScreen"></i></div>
-      <div class="tool-icon" @click="autoLayout"><i class="el-icon-ksd-auto"></i></div>
-    </div>
-    <!-- 右侧面板组 -->
-    <!-- dimension面板  index 0-->
-    <transition name="bounceright">
-      <div class="panel-box panel-dimension" @mousedown.stop="activePanel('dimension')" :style="panelStyle('dimension')" v-if="panelAppear.dimension.display">
-        <div class="panel-title" @mousedown="activePanel('dimension')" v-drag:change.right.top="panelAppear.dimension">
-          <span><i class="el-icon-ksd-dimension"></i></span>
-          <span class="title">{{$t('kylinLang.common.dimension')}} <template v-if="(modelRender.dimensions || []).length">({{modelRender.dimensions.length}})</template></span>
-          <span class="close" @click="toggleMenu('dimension')"><i class="el-icon-ksd-close"></i></span>
-        </div>
-        <div class="panel-sub-title">
-          <div class="action_group" :class="{'is_active': !isShowCheckbox}">
-            <!-- <span class="action_btn" @click="addCCDimension">
-              <i class="el-icon-ksd-project_add"></i>
-              <span>{{$t('add')}}</span>
-            </span> -->
-            <span class="action_btn" @click="batchSetDimension">
-              <i class="el-icon-ksd-backup"></i>
-              <span>{{$t('batchAdd')}}</span>
-            </span>
-            <span class="action_btn" :class="{'disabled': allDimension.length==0}" @click="toggleCheckbox">
-              <i class="el-icon-ksd-batch_delete"></i>
-              <span>{{$t('batchDel')}}</span>
-            </span>
-          </div>
-          <div
-          class="batch_group"
-          :class="{'is_active': isShowCheckbox}"
-          :style="{transform: isShowCheckbox ? 'translateX(0)' : 'translateX(100%)'}"
-          >
-            <span class="action_btn" :class="{'disabled': isDisableBatchCheck}" @click="toggleCheckAllDimension">
-              <i class="el-icon-ksd-batch_uncheck" v-if="dimensionSelectedList.length==modelRender.dimensions.length || (modelInstance.second_storage_enabled||isHybridModel)&&dimensionSelectedList.length+1==modelRender.dimensions.length&&!isDisableBatchCheck"></i>
-              <i class="el-icon-ksd-batch" v-else></i>
-              <span v-if="dimensionSelectedList.length==modelRender.dimensions.length || (modelInstance.second_storage_enabled || isHybridModel) && dimensionSelectedList.length+1 == modelRender.dimensions.length && !isDisableBatchCheck">{{$t('unCheckAll')}}</span>
-              <span v-else>{{$t('checkAll')}}</span>
-            </span>
-            <span class="action_btn" :class="{'disabled': dimensionSelectedList.length === 0}" @click="deleteDimenisons">
-              <i class="el-icon-ksd-table_delete"></i>
-              <span>{{$t('delete')}}</span>
-            </span>
-            <span class="action_btn" @click="toggleCheckbox">
-              <i class="el-icon-ksd-back"></i>
-              <span>{{$t('back')}}</span>
-            </span>
-          </div>
-        </div>
-        <div class="panel-main-content" @dragover='($event) => {allowDropColumnToPanle($event)}' @drop='(e) => {dropColumnToPanel(e, "dimension")}'>
-          <div class="content-scroll-layout" v-if="allDimension.length" v-scroll.observe.reactive @scroll-bottom="boardScrollBottom('dimension')">
-            <ul class="dimension-list">
-              <li v-for="(d, i) in allDimension" :key="`${d.name}_${i}`" :class="{'is-checked':dimensionSelectedList.indexOf(d.name)>-1}">
-                <span :class="['ksd-nobr-text', {'checkbox-text-overflow': isShowCheckbox}]">
-                  <el-checkbox v-model="dimensionSelectedList" v-if="isShowCheckbox" :disabled="(modelInstance.second_storage_enabled||isHybridModel)&&modelInstance.partition_desc&&modelInstance.partition_desc.partition_date_column===d.column" :label="d.name" class="text">{{d.name}}</el-checkbox>
-                  <span v-else :title="d.name" class="text">{{d.name}}</span>
-                  <span class="icon-group">
-                    <el-tooltip :content="disableDelDimTips" placement="top-end" :disabled="!((modelInstance.second_storage_enabled||isHybridModel)&&modelInstance.partition_desc&&modelInstance.partition_desc.partition_date_column===d.column)">
-                      <span class="icon-span" :class="{'is-disabled': (modelInstance.second_storage_enabled||isHybridModel)&&modelInstance.partition_desc&&modelInstance.partition_desc.partition_date_column===d.column}" @click="deleteDimenison(d)"><i class="el-icon-ksd-table_delete"></i></span>
-                    </el-tooltip>
-                    <span class="icon-span"><i class="el-icon-ksd-table_edit" @click="editDimension(d, i)"></i></span>
-                    <span class="li-type ky-option-sub-info">{{d.datatype && d.datatype.toLocaleLowerCase()}}</span>
+              <!-- 渲染可计算列 -->
+              <template v-if="t.kind=== 'FACT'">
+                <li :class="['column-li', 'column-li-cc', {'is-link': col.isPK || col.isFK}]"
+                  @drop='(e) => {dropColumn(e, {name: col.columnName }, t)}'
+                  @dragover="(e) => {dragColumnEnter(e, t, col)}"
+                  @dragleave="dragColumnLeave"
+                  @dragstart="(e) => {dragColumns(e, col, t)}"
+                  @mouseleave="(e) => handleMouseLeave(e, t, col)"
+                  @mouseenter="(e) => handleMouseEnterColumn(e, t, col)"
+                  draggable
+                  v-for="col in getCurrentColumns(modelRender, 'cc')"
+                  :key="col.name"
+                >
+                  <span class="ksd-nobr-text">
+                    <span class="col-type-icon">
+                      <span class="is-pfk" v-show="col.isPK || col.isFK">{{`${col.isFK && col.isPK ? 'FK PK' : col.isFK ? 'FK' : 'PK'}`}}</span><i :class="columnTypeIconMap(col.datatype)"></i>
+                    </span>
+                    <span :class="['col-name']" v-custom-tooltip="{text: col.columnName, w: 30, effect: 'dark', 'popper-class': 'popper--small', 'visible-arrow': false, position: 'bottom-start', observerId: t.guid}">{{col.columnName}}</span>
                   </span>
-                </span>
-              </li>
+                </li>
+              </template>
+              <li class="li-load-more" v-if="t.hasMoreColumns && t.hasScrollEnd && !showOnlyConnectedColumn"><i class="el-ksd-icon-loading_16"></i></li>
             </ul>
           </div>
-          <kylin-nodata v-else></kylin-nodata>
+          <kylin-nodata v-show="t.showColumns.length === 0 && t.filterColumnChar" :content="$t('noResults')"></kylin-nodata>
+          <!-- 拖动操纵 -->
+          <DragBar :dragData="t.drawSize" :dragZoom="modelRender.zoom"/>
+          <!-- 拖动操纵 -->
         </div>
-        <!-- 拖动操纵 -->
-        <DragBar :dragData="panelAppear.dimension"/>
-        <!-- 拖动操纵 -->
+        <!-- table box end -->
       </div>
-    </transition>
-    <!-- measure面板  index 1-->
-    <transition name="bounceright">
-      <div class="panel-box panel-measure" @mousedown.stop="activePanel('measure')" :style="panelStyle('measure')"  v-if="panelAppear.measure.display">
-        <div class="panel-title" @mousedown="activePanel('measure')" v-drag:change.right.top="panelAppear.measure">
-          <span><i class="el-icon-ksd-measure"></i></span>
-          <span class="title">{{$t('kylinLang.common.measure')}}<template v-if="modelRender.all_measures.length">({{modelRender.all_measures.length}})</template></span>
-          <span class="close" @click="toggleMenu('measure')"><i class="el-icon-ksd-close"></i></span>
+      <!-- datasource面板  index 3-->
+      <div class="tool-icon icon-ds" v-if="panelAppear.datasource.icon_display" :class="{active: panelAppear.datasource.display}" v-event-stop @click="toggleMenu('datasource')"><i class="el-icon-ksd-data_source"></i></div>
+      <transition name="bounceleft">
+        <div class="panel-box panel-datasource"  v-show="panelAppear.datasource.display" :style="panelStyle('datasource')" v-event-stop  @click.stop>
+          <div class="panel-title" v-drag:change.left.top="panelAppear.datasource"><span class="title">{{$t('kylinLang.common.dataSource')}}</span><span class="close" @click="toggleMenu('datasource')"><i class="el-icon-ksd-close"></i></span></div>
+          <DataSourceBar
+            :ignore-node-types="['column']"
+            class="tree-box"
+            :class="{'iframeTreeBox': $store.state.config.platform === 'iframe'}"
+            ref="datasourceTree"
+            :project-name="currentSelectedProject"
+            :is-show-load-source="true"
+            :is-show-load-table="datasourceActions.includes('loadSource')"
+            :is-show-load-table-inner-btn="datasourceActions.includes('loadSource')"
+            :is-show-settings="false"
+            :is-show-action-group="false"
+            :is-expand-on-click-node="false"
+            :expand-node-types="['datasource', 'database']"
+            :draggable-node-types="['table']"
+            :searchable-node-types="['table']"
+            :is-model-have-fact="modelInstance && !!modelInstance.fact_table"
+            :is-second-storage-enabled="modelInstance && modelInstance.second_storage_enabled"
+            @drag="dragTable">
+          </DataSourceBar>
+          <!-- </div> -->
+          <!-- 拖动操纵 -->
+          <DragBar :dragData="panelAppear.datasource"/>
+          <!-- 拖动操纵 -->
         </div>
-        <div class="panel-sub-title">
-          <div class="action_group" :class="{'is_active': !isShowMeaCheckbox}">
-            <span class="action_btn" @click="addNewMeasure">
-              <i class="el-icon-ksd-project_add"></i>
-              <span>{{$t('add')}}</span>
-            </span>
-            <span class="action_btn" @click="batchSetMeasure">
-              <i class="el-icon-ksd-backup"></i>
-              <span>{{$t('batchAdd')}}</span>
-            </span>
-            <span class="action_btn" @click="toggleMeaCheckbox" :class="{'disabled': canDelMeasureAll}">
-              <i class="el-icon-ksd-batch_delete"></i>
-              <span>{{$t('batchDel')}}</span>
-            </span>
+      </transition>
+      <!-- datasource面板  end-->
+      <div class="tool-icon-group" v-event-stop>
+        <div class="tool-icon broken-icon" v-if="panelAppear.brokenFocus.icon_display" @click="focusBrokenLinkedTable">
+          <i class="el-icon-ksd-broken_disconnect"></i>
+        </div>
+        <div class="tool-icon" v-if="panelAppear.dimension.icon_display" :class="{active: panelAppear.dimension.display}" @click="toggleMenu('dimension')">D</div>
+        <div class="tool-icon" v-if="panelAppear.measure.icon_display" :class="{active: panelAppear.measure.display}" @click="toggleMenu('measure')">M</div>
+        <div class="tool-icon" v-if="panelAppear.cc.icon_display" :class="{active: panelAppear.cc.display}" @click="toggleMenu('cc')"><i class="el-icon-ksd-computed_column"></i></div>
+        <!-- <div class="tool-icon" v-if="panelAppear.search.icon_display" :class="{active: panelAppear.search.display}" @click="toggleMenu('search')">
+          <i class="el-icon-ksd-search"></i>
+          <span class="new-icon">New</span>
+        </div> -->
+      </div>
+      <ModelNavigationTools :zoom="modelRender.zoom" @command="handleActionsCommand" @addZoom="addZoom" @reduceZoom="reduceZoom" @autoLayout="autoLayout"/>
+      <!-- 右侧面板组 -->
+      <!-- dimension面板  index 0-->
+      <transition name="bounceright">
+        <div class="panel-box panel-dimension" @mousedown.stop="activePanel('dimension')" :style="panelStyle('dimension')" v-if="panelAppear.dimension.display">
+          <div class="panel-title" @mousedown="activePanel('dimension')" v-drag:change.right.top="panelAppear.dimension">
+            <span><i class="el-icon-ksd-dimension"></i></span>
+            <span class="title">{{$t('kylinLang.common.dimension')}} <template v-if="(modelRender.dimensions || []).length">({{modelRender.dimensions.length}})</template></span>
+            <span class="close" @click="toggleMenu('dimension')"><i class="el-icon-ksd-close"></i></span>
           </div>
-          <div
+          <div class="panel-sub-title">
+            <div class="action_group" :class="{'is_active': !isShowCheckbox}">
+              <!-- <span class="action_btn" @click="addCCDimension">
+                <i class="el-icon-ksd-project_add"></i>
+                <span>{{$t('add')}}</span>
+              </span> -->
+              <span class="action_btn" @click="batchSetDimension">
+                <i class="el-icon-ksd-backup"></i>
+                <span>{{$t('batchAdd')}}</span>
+              </span>
+              <span class="action_btn" :class="{'disabled': allDimension.length==0}" @click="toggleCheckbox">
+                <i class="el-icon-ksd-batch_delete"></i>
+                <span>{{$t('batchDel')}}</span>
+              </span>
+            </div>
+            <div
             class="batch_group"
-            :class="{'is_active': isShowMeaCheckbox}"
-            :style="{transform: isShowMeaCheckbox ? 'translateX(0)' : 'translateX(100%)'}"
-          >
-            <span class="action_btn" @click="toggleCheckAllMeasure">
-              <i class="el-icon-ksd-batch" v-if="measureSelectedList.length > 0 && measureSelectedList.length === toggleMeasureStatus"></i>
-              <i class="el-icon-ksd-batch_uncheck" v-else></i>
-              <span v-if="measureSelectedList.length > 0 && measureSelectedList.length === toggleMeasureStatus">{{$t('unCheckAll')}}</span>
-              <span v-else>{{$t('checkAll')}}</span>
-            </span>
-            <span class="action_btn" :class="{'disabled': measureSelectedList.length==0}" @click="deleteMeasures">
-              <i class="el-icon-ksd-table_delete"></i>
-              <span>{{$t('delete')}}</span>
-            </span>
-            <span class="action_btn" @click="toggleMeaCheckbox">
-              <i class="el-icon-ksd-back"></i>
-              <span>{{$t('back')}}</span>
-            </span>
+            :class="{'is_active': isShowCheckbox}"
+            :style="{transform: isShowCheckbox ? 'translateX(0)' : 'translateX(100%)'}"
+            >
+              <span class="action_btn" :class="{'disabled': isDisableBatchCheck}" @click="toggleCheckAllDimension">
+                <i class="el-icon-ksd-batch_uncheck" v-if="dimensionSelectedList.length==modelRender.dimensions.length || (modelInstance.second_storage_enabled||isHybridModel)&&dimensionSelectedList.length+1==modelRender.dimensions.length&&!isDisableBatchCheck"></i>
+                <i class="el-icon-ksd-batch" v-else></i>
+                <span v-if="dimensionSelectedList.length==modelRender.dimensions.length || (modelInstance.second_storage_enabled || isHybridModel) && dimensionSelectedList.length+1 == modelRender.dimensions.length && !isDisableBatchCheck">{{$t('unCheckAll')}}</span>
+                <span v-else>{{$t('checkAll')}}</span>
+              </span>
+              <span class="action_btn" :class="{'disabled': dimensionSelectedList.length === 0}" @click="deleteDimenisons">
+                <i class="el-icon-ksd-table_delete"></i>
+                <span>{{$t('delete')}}</span>
+              </span>
+              <span class="action_btn" @click="toggleCheckbox">
+                <i class="el-icon-ksd-back"></i>
+                <span>{{$t('back')}}</span>
+              </span>
+            </div>
           </div>
-        </div>
-        <div class="panel-main-content" @dragover='($event) => {allowDropColumnToPanle($event)}' @drop='(e) => {dropColumnToPanel(e, "measure")}'>
-          <div class="content-scroll-layout" v-if="allMeasure.length" v-scroll.observe.reactive @scroll-bottom="boardScrollBottom('measure')">
-            <ul class="measure-list">
-              <li v-for="m in allMeasure" :key="m.name" :class="{'is-checked':measureSelectedList.indexOf(m.name)>-1, 'error-measure': ['SUM', 'PERCENTILE_APPROX'].includes(m.expression) && m.return_type && m.return_type.indexOf('varchar') > -1}">
-                <span :class="['ksd-nobr-text', {'checkbox-text-overflow': isShowMeaCheckbox}]">
-                  <el-tooltip class="count-all" :offset="isShowMeaCheckbox ? 50 : 60" :content="m.name ==='COUNT_ALL' ? $t('disabledConstantMeasureTip') : $t('measureRuleErrorTip', {type: m.expression})" effect="dark" placement="bottom" :disabled="!(['SUM', 'PERCENTILE_APPROX'].includes(m.expression) && m.return_type && m.return_type.indexOf('varchar') > -1) && m.name !== 'COUNT_ALL'">
-                    <span>
-                      <el-checkbox v-model="measureSelectedList" v-if="isShowMeaCheckbox" :disabled="m.name === 'COUNT_ALL'" :label="m.name" class="text">{{m.name}}</el-checkbox>
-                      <span v-else class="text">{{m.name}}</span>
+          <div class="panel-main-content" @dragover='($event) => {allowDropColumnToPanle($event)}' @drop='(e) => {dropColumnToPanel(e, "dimension")}'>
+            <div class="content-scroll-layout" v-if="allDimension.length" v-scroll.observe.reactive @scroll-bottom="boardScrollBottom('dimension')">
+              <ul class="dimension-list">
+                <li v-for="(d, i) in allDimension" :key="`${d.name}_${i}`" :class="{'is-checked':dimensionSelectedList.indexOf(d.name)>-1}">
+                  <span :class="['ksd-nobr-text', {'checkbox-text-overflow': isShowCheckbox}]">
+                    <el-checkbox v-model="dimensionSelectedList" v-if="isShowCheckbox" :disabled="(modelInstance.second_storage_enabled||isHybridModel)&&modelInstance.partition_desc&&modelInstance.partition_desc.partition_date_column===d.column" :label="d.name" class="text">{{d.name}}</el-checkbox>
+                    <span v-else :title="d.name" class="text">{{d.name}}</span>
+                    <span class="icon-group">
+                      <el-tooltip :content="disableDelDimTips" placement="top-end" :disabled="!((modelInstance.second_storage_enabled||isHybridModel)&&modelInstance.partition_desc&&modelInstance.partition_desc.partition_date_column===d.column)">
+                        <span class="icon-span" :class="{'is-disabled': (modelInstance.second_storage_enabled||isHybridModel)&&modelInstance.partition_desc&&modelInstance.partition_desc.partition_date_column===d.column}" @click="deleteDimenison(d)"><i class="el-icon-ksd-table_delete"></i></span>
+                      </el-tooltip>
+                      <span class="icon-span"><i class="el-icon-ksd-table_edit" @click="editDimension(d, i)"></i></span>
+                      <span class="li-type ky-option-sub-info">{{d.datatype && d.datatype.toLocaleLowerCase()}}</span>
                     </span>
-                  </el-tooltip>
-                  <span class="icon-group">
-                    <span class="icon-span" v-if="m.name !== 'COUNT_ALL'"><i class="el-icon-ksd-table_delete" @click="deleteMeasure(m.name)"></i></span>
-                    <span class="icon-span" v-if="m.name !== 'COUNT_ALL'"><i class="el-icon-ksd-table_edit" @click="editMeasure(m)"></i></span>
-                    <span class="li-type ky-option-sub-info">{{m.return_type && m.return_type.toLocaleLowerCase()}}</span>
                   </span>
-                </span>
-              </li>
-            </ul>
+                </li>
+              </ul>
+            </div>
+            <kylin-nodata v-else></kylin-nodata>
           </div>
-          <kylin-nodata v-else></kylin-nodata>
-        </div>
-        <!-- 拖动操纵 -->
-        <DragBar :dragData="panelAppear.measure"/>
-        <!-- 拖动操纵 -->
-      </div>
-    </transition>
-    <!-- 可计算列 -->
-    <transition name="bounceright">
-      <div class="panel-box panel-cc" @mousedown.stop="activePanel('cc')" :style="panelStyle('cc')"  v-if="panelAppear.cc.display">
-        <div class="panel-title" @mousedown="activePanel('cc')" v-drag:change.right.top="panelAppear.cc">
-          <span><i class="el-ksd-icon-auto_computed_column_old"></i></span>
-          <span class="title">{{$t('kylinLang.model.computedColumn')}} <template v-if="modelRender.computed_columns.length">({{modelRender.computed_columns.length}})</template></span>
-          <span class="close" @click="toggleMenu('cc')"><i class="el-icon-ksd-close"></i></span>
+          <!-- 拖动操纵 -->
+          <DragBar :dragData="panelAppear.dimension"/>
+          <!-- 拖动操纵 -->
         </div>
-        <div class="panel-sub-title">
-          <div class="action_group" :class="{'is_active': !isShowCCCheckbox}">
-            <el-tooltip :content="$t('forbidenCreateCCTip')" :disabled="!isHybridModel">
-              <span :class="['action_btn', {'disabled': isHybridModel}]" @click="!isHybridModel && addCC()">
+      </transition>
+      <!-- measure面板  index 1-->
+      <transition name="bounceright">
+        <div class="panel-box panel-measure" @mousedown.stop="activePanel('measure')" :style="panelStyle('measure')"  v-if="panelAppear.measure.display">
+          <div class="panel-title" @mousedown="activePanel('measure')" v-drag:change.right.top="panelAppear.measure">
+            <span><i class="el-icon-ksd-measure"></i></span>
+            <span class="title">{{$t('kylinLang.common.measure')}}<template v-if="modelRender.all_measures.length">({{modelRender.all_measures.length}})</template></span>
+            <span class="close" @click="toggleMenu('measure')"><i class="el-icon-ksd-close"></i></span>
+          </div>
+          <div class="panel-sub-title">
+            <div class="action_group" :class="{'is_active': !isShowMeaCheckbox}">
+              <span class="action_btn" @click="addNewMeasure">
                 <i class="el-icon-ksd-project_add"></i>
                 <span>{{$t('add')}}</span>
               </span>
-            </el-tooltip>
-            <span class="action_btn" @click="toggleCCCheckbox" :class="{'active': isShowCCCheckbox}">
-              <i class="el-icon-ksd-batch_delete"></i>
-              <span>{{$t('batchDel')}}</span>
-            </span>
+              <span class="action_btn" @click="batchSetMeasure">
+                <i class="el-icon-ksd-backup"></i>
+                <span>{{$t('batchAdd')}}</span>
+              </span>
+              <span class="action_btn" @click="toggleMeaCheckbox" :class="{'disabled': canDelMeasureAll}">
+                <i class="el-icon-ksd-batch_delete"></i>
+                <span>{{$t('batchDel')}}</span>
+              </span>
+            </div>
+            <div
+              class="batch_group"
+              :class="{'is_active': isShowMeaCheckbox}"
+              :style="{transform: isShowMeaCheckbox ? 'translateX(0)' : 'translateX(100%)'}"
+            >
+              <span class="action_btn" @click="toggleCheckAllMeasure">
+                <i class="el-icon-ksd-batch" v-if="measureSelectedList.length > 0 && measureSelectedList.length === toggleMeasureStatus"></i>
+                <i class="el-icon-ksd-batch_uncheck" v-else></i>
+                <span v-if="measureSelectedList.length > 0 && measureSelectedList.length === toggleMeasureStatus">{{$t('unCheckAll')}}</span>
+                <span v-else>{{$t('checkAll')}}</span>
+              </span>
+              <span class="action_btn" :class="{'disabled': measureSelectedList.length==0}" @click="deleteMeasures">
+                <i class="el-icon-ksd-table_delete"></i>
+                <span>{{$t('delete')}}</span>
+              </span>
+              <span class="action_btn" @click="toggleMeaCheckbox">
+                <i class="el-icon-ksd-back"></i>
+                <span>{{$t('back')}}</span>
+              </span>
+            </div>
           </div>
-          <div
-            class="batch_group"
-            :class="{'is_active': isShowCCCheckbox}"
-          >
-            <span class="action_btn" @click="toggleCheckAllCC">
-              <i class="el-icon-ksd-batch_uncheck" v-if="ccSelectedList.length==modelRender.computed_columns.length"></i>
-              <i class="el-icon-ksd-batch" v-else></i>
-              <span v-if="ccSelectedList.length==modelRender.computed_columns.length">{{$t('unCheckAll')}}</span>
-              <span v-else>{{$t('checkAll')}}</span>
-            </span>
-            <span class="action_btn" :class="{'disabled': ccSelectedList.length==0}" @click="delCCs">
-              <i class="el-icon-ksd-table_delete"></i>
-              <span>{{$t('delete')}}</span>
-            </span>
-            <span class="action_btn" @click="toggleCCCheckbox">
-              <i class="el-icon-ksd-back"></i>
-              <span>{{$t('back')}}</span>
-            </span>
+          <div class="panel-main-content" @dragover='($event) => {allowDropColumnToPanle($event)}' @drop='(e) => {dropColumnToPanel(e, "measure")}'>
+            <div class="content-scroll-layout" v-if="allMeasure.length" v-scroll.observe.reactive @scroll-bottom="boardScrollBottom('measure')">
+              <ul class="measure-list">
+                <li v-for="m in allMeasure" :key="m.name" :class="{'is-checked':measureSelectedList.indexOf(m.name)>-1, 'error-measure': ['SUM', 'PERCENTILE_APPROX'].includes(m.expression) && m.return_type && m.return_type.indexOf('varchar') > -1}">
+                  <span :class="['ksd-nobr-text', {'checkbox-text-overflow': isShowMeaCheckbox}]">
+                    <el-tooltip class="count-all" :offset="isShowMeaCheckbox ? 50 : 60" :content="m.name ==='COUNT_ALL' ? $t('disabledConstantMeasureTip') : $t('measureRuleErrorTip', {type: m.expression})" effect="dark" placement="bottom" :disabled="!(['SUM', 'PERCENTILE_APPROX'].includes(m.expression) && m.return_type && m.return_type.indexOf('varchar') > -1) && m.name !== 'COUNT_ALL'">
+                      <span>
+                        <el-checkbox v-model="measureSelectedList" v-if="isShowMeaCheckbox" :disabled="m.name === 'COUNT_ALL'" :label="m.name" class="text">{{m.name}}</el-checkbox>
+                        <span v-else class="text">{{m.name}}</span>
+                      </span>
+                    </el-tooltip>
+                    <span class="icon-group">
+                      <span class="icon-span" v-if="m.name !== 'COUNT_ALL'"><i class="el-icon-ksd-table_delete" @click="deleteMeasure(m.name)"></i></span>
+                      <span class="icon-span" v-if="m.name !== 'COUNT_ALL'"><i class="el-icon-ksd-table_edit" @click="editMeasure(m)"></i></span>
+                      <span class="li-type ky-option-sub-info">{{m.return_type && m.return_type.toLocaleLowerCase()}}</span>
+                    </span>
+                  </span>
+                </li>
+              </ul>
+            </div>
+            <kylin-nodata v-else></kylin-nodata>
           </div>
+          <!-- 拖动操纵 -->
+          <DragBar :dragData="panelAppear.measure"/>
+          <!-- 拖动操纵 -->
         </div>
-        <div class="panel-main-content" v-scroll.obverse  v-if="modelRender.computed_columns.length">
-          <ul class="cc-list">
-            <li v-for="m in modelRender.computed_columns" :key="m.name" :class="{'is-checked':ccSelectedList.indexOf(m.columnName)>-1}">
-              <span :class="['ksd-nobr-text', {'checkbox-text-overflow': isShowCCCheckbox}]">
-                <el-checkbox v-model="ccSelectedList" v-if="isShowCCCheckbox" :label="m.columnName" class="text">{{m.columnName}}</el-checkbox>
-                <span v-else class="text">{{m.columnName}}</span>
-                <span class="icon-group">
-                  <span class="icon-span"><i class="el-icon-ksd-table_delete" @click="delCC(m.columnName)"></i></span>
-                  <span class="icon-span"><i class="el-icon-ksd-table_edit" @click="editCC(m)"></i></span>
-                  <span class="li-type ky-option-sub-info">{{m.datatype && m.datatype.toLocaleLowerCase()}}</span>
+      </transition>
+      <!-- 可计算列 -->
+      <transition name="bounceright">
+        <div class="panel-box panel-cc" @mousedown.stop="activePanel('cc')" :style="panelStyle('cc')"  v-if="panelAppear.cc.display">
+          <div class="panel-title" @mousedown="activePanel('cc')" v-drag:change.right.top="panelAppear.cc">
+            <span><i class="el-ksd-icon-auto_computed_column_old"></i></span>
+            <span class="title">{{$t('kylinLang.model.computedColumn')}} <template v-if="modelRender.computed_columns.length">({{modelRender.computed_columns.length}})</template></span>
+            <span class="close" @click="toggleMenu('cc')"><i class="el-icon-ksd-close"></i></span>
+          </div>
+          <div class="panel-sub-title">
+            <div class="action_group" :class="{'is_active': !isShowCCCheckbox}">
+              <el-tooltip :content="$t('forbidenCreateCCTip')" :disabled="!isHybridModel">
+                <span :class="['action_btn', {'disabled': isHybridModel}]" @click="!isHybridModel && addCC()">
+                  <i class="el-icon-ksd-project_add"></i>
+                  <span>{{$t('add')}}</span>
                 </span>
+              </el-tooltip>
+              <span class="action_btn" @click="toggleCCCheckbox" :class="{'active': isShowCCCheckbox}">
+                <i class="el-icon-ksd-batch_delete"></i>
+                <span>{{$t('batchDel')}}</span>
               </span>
-            </li>
-          </ul>
-        </div>
-        <kylin-nodata v-if="!modelRender.computed_columns.length"></kylin-nodata>
-        <!-- 拖动操纵 -->
-        <DragBar :dragData="panelAppear.cc"/>
-        <!-- 拖动操纵 -->
-      </div>
-    </transition>
-    <!-- 搜索面板 -->
-    <transition name="bouncecenter">
-      <div class="panel-search-box panel-box" :class="{'full-screen': isFullScreen}"  v-event-stop :style="panelStyle('search')" v-if="panelAppear.search.display">
-        <el-row :gutter="20">
-          <el-col :span="14" :offset="5">
-            <el-alert class="search-action-result" v-if="modelSearchActionSuccessTip" v-timer-hide:2
-              :title="modelSearchActionSuccessTip"
-              type="success"
-              :closable="false"
-              show-icon>
-            </el-alert>
-            <el-input @input="searchModelEverything"  clearable class="search-input" :placeholder="$t('searchInputPlaceHolder')" v-model="modelGlobalSearch" prefix-icon="el-ksd-icon-search_22"></el-input>
-            <transition name="bounceleft">
-              <div v-scroll.reactive class="search-result-box" v-keyborad-select="{scope:'.search-content', searchKey: modelGlobalSearch}" v-if="modelGlobalSearch && showSearchResult" v-search-highlight="{scope:'.search-name', hightlight: modelGlobalSearch}">
-                <div>
-                <div class="search-group" v-for="(k,v) in searchResultData" :key="v">
-                  <ul>
-                    <li class="search-content" v-for="(x, i) in k" @click="(e) => {selectResult(e, x)}" :key="x.action + x.name + i"><span class="search-category">[{{$t(x.i18n)}}]</span> <span class="search-name">{{x.name}}</span><span v-html="x.extraInfo"></span></li>
-                  </ul>
-                  <div class="ky-line"></div>
-                </div>
-                <div v-show="Object.keys(searchResultData).length === 0" class="search-noresult">{{$t('kylinLang.common.noResults')}}</div>
-              </div>
             </div>
-            </transition>
-          </el-col>
-          <el-col :span="5">
-            <div class="search-action-list" v-if="modelSearchActionHistoryList && modelSearchActionHistoryList.length">
-              <div class="action-list-title">{{$t('searchHistory')}}</div>
-              <div class="action-content" v-for="(item, index) in modelSearchActionHistoryList" :key="index">
-                <div class="action-title">
-                  <i :class="item.icon" class="ksd-mr-6 search-list-icon"></i>
-                  <div class="action-desc" v-html="item.title"></div>
-                </div>
-                <div class="action-detail"></div>
-              </div>
+            <div
+              class="batch_group"
+              :class="{'is_active': isShowCCCheckbox}"
+            >
+              <span class="action_btn" @click="toggleCheckAllCC">
+                <i class="el-icon-ksd-batch_uncheck" v-if="ccSelectedList.length==modelRender.computed_columns.length"></i>
+                <i class="el-icon-ksd-batch" v-else></i>
+                <span v-if="ccSelectedList.length==modelRender.computed_columns.length">{{$t('unCheckAll')}}</span>
+                <span v-else>{{$t('checkAll')}}</span>
+              </span>
+              <span class="action_btn" :class="{'disabled': ccSelectedList.length==0}" @click="delCCs">
+                <i class="el-icon-ksd-table_delete"></i>
+                <span>{{$t('delete')}}</span>
+              </span>
+              <span class="action_btn" @click="toggleCCCheckbox">
+                <i class="el-icon-ksd-back"></i>
+                <span>{{$t('back')}}</span>
+              </span>
             </div>
-          </el-col>
-        </el-row>
-        <div class="close" @click="toggleMenu('search')" v-global-key-event.esc="() => {toggleMenu('search')}">
-          <i class="el-icon-ksd-close ksd-mt-12"></i><br/>
-          <span>ESC</span>
+          </div>
+          <div class="panel-main-content" v-scroll.obverse  v-if="modelRender.computed_columns.length">
+            <ul class="cc-list">
+              <li v-for="m in modelRender.computed_columns" :key="m.name" :class="{'is-checked':ccSelectedList.indexOf(m.columnName)>-1}">
+                <span :class="['ksd-nobr-text', {'checkbox-text-overflow': isShowCCCheckbox}]">
+                  <el-checkbox v-model="ccSelectedList" v-if="isShowCCCheckbox" :label="m.columnName" class="text">{{m.columnName}}</el-checkbox>
+                  <span v-else class="text">{{m.columnName}}</span>
+                  <span class="icon-group">
+                    <span class="icon-span"><i class="el-icon-ksd-table_delete" @click="delCC(m.columnName)"></i></span>
+                    <span class="icon-span"><i class="el-icon-ksd-table_edit" @click="editCC(m)"></i></span>
+                    <span class="li-type ky-option-sub-info">{{m.datatype && m.datatype.toLocaleLowerCase()}}</span>
+                  </span>
+                </span>
+              </li>
+            </ul>
+          </div>
+          <kylin-nodata v-if="!modelRender.computed_columns.length"></kylin-nodata>
+          <p class="has-no-connected-column" v-if="t.spreadOut && showOnlyConnectedColumn && getCurrentColumns(t).length === 0 && getCurrentColumns(modelRender, 'cc').length === 0">{{$t('noConnectedColumn')}}</p>
+          <!-- 拖动操纵 -->
+          <DragBar :dragData="panelAppear.cc"/>
+          <!-- 拖动操纵 -->
         </div>
-      </div>
-    </transition>
-
+      </transition>
+      <!-- 搜索面板 -->
 
-    <ModelSaveConfig/>
-    <DimensionModal/>
-    <BatchMeasureModal @betchMeasures="updateBetchMeasure"/>
-    <TableJoinModal/>
-    <AddMeasure
-      v-if="measureVisible"
-      :isEditMeasure="isEditMeasure"
-      :measureObj="measureObj"
-      :modelInstance="modelInstance"
-      :isHybridModel="isHybridModel"
-      v-on:closeAddMeasureDia="closeAddMeasureDia">
-    </AddMeasure>
-    <SingleDimensionModal/>
-    <AddCC/>
-    <ShowCC/>
-    <ActionUpdateGuide v-if="showModelGuide" @close-guide="showModelGuide = false" />
+      <ModelSaveConfig/>
+      <DimensionModal/>
+      <BatchMeasureModal @betchMeasures="updateBetchMeasure"/>
+      <TableJoinModal/>
+      <AddMeasure
+        v-if="measureVisible"
+        :isEditMeasure="isEditMeasure"
+        :measureObj="measureObj"
+        :modelInstance="modelInstance"
+        :isHybridModel="isHybridModel"
+        v-on:closeAddMeasureDia="closeAddMeasureDia">
+      </AddMeasure>
+      <SingleDimensionModal/>
+      <AddCC/>
+      <ShowCC/>
+      <ActionUpdateGuide v-if="showModelGuide" @close-guide="showModelGuide = false" />
 
-    <el-dialog
-      :title="$t('kylinLang.common.tip')"
-      :visible.sync="gotoIndexdialogVisible"
-      width="30%"
-      append-to-body
-      limited-area
-      class="add-index-confirm-dialog"
-      :close-on-click-modal="false"
-      :show-close="false">
-      <i class="el-icon-success ksd-mr-10 ky-dialog-icon"></i>
-      <div class="ksd-pl-26">
-        <div>{{$t('saveSuccessTip')}}</div>
-        <div>
-          <span v-if="getBaseIndexCount(saveModelResponse).createBaseIndexNum > 0">{{$t('createAndBuildBaseIndexTips', {createBaseIndexNum: getBaseIndexCount(saveModelResponse).createBaseIndexNum})}}</span>
-          <span v-if="getBaseIndexCount(saveModelResponse).createBaseIndexNum === 0">{{$t('addIndexTips')}}</span>
-          <span v-else>{{$t('addIndexAndBaseIndex')}}</span>
+      <el-dialog
+        :title="$t('kylinLang.common.tip')"
+        :visible.sync="gotoIndexdialogVisible"
+        width="30%"
+        append-to-body
+        limited-area
+        class="add-index-confirm-dialog"
+        :close-on-click-modal="false"
+        :show-close="false">
+        <i class="el-icon-success ksd-mr-10 ky-dialog-icon"></i>
+        <div class="ksd-pl-26">
+          <div>{{$t('saveSuccessTip')}}</div>
+          <div>
+            <span v-if="getBaseIndexCount(saveModelResponse).createBaseIndexNum > 0">{{$t('createAndBuildBaseIndexTips', {createBaseIndexNum: getBaseIndexCount(saveModelResponse).createBaseIndexNum})}}</span>
+            <span v-if="getBaseIndexCount(saveModelResponse).createBaseIndexNum === 0">{{$t('addIndexTips')}}</span>
+            <span v-else>{{$t('addIndexAndBaseIndex')}}</span>
+          </div>
         </div>
-      </div>
-      <span slot="footer" class="dialog-footer" v-if="gotoIndexdialogVisible">
-        <el-button plain @click="ignoreAddIndex">{{getBaseIndexCount(saveModelResponse).createBaseIndexNum > 0 ? $t('kylinLang.common.cancel') : $t('ignoreaddIndexTip')}}</el-button>
-        <el-button type="primary" @click="willAddIndex">{{getBaseIndexCount(saveModelResponse).createBaseIndexNum > 0 ? $t('viewIndexes') : $t('addIndex')}}</el-button>
-      </span>
-    </el-dialog>
+        <span slot="footer" class="dialog-footer" v-if="gotoIndexdialogVisible">
+          <el-button plain @click="ignoreAddIndex">{{getBaseIndexCount(saveModelResponse).createBaseIndexNum > 0 ? $t('kylinLang.common.cancel') : $t('ignoreaddIndexTip')}}</el-button>
+          <el-button type="primary" @click="willAddIndex">{{getBaseIndexCount(saveModelResponse).createBaseIndexNum > 0 ? $t('viewIndexes') : $t('addIndex')}}</el-button>
+        </span>
+      </el-dialog>
+    </div>
   </div>
 </template>
 <script>
@@ -474,8 +463,8 @@ import { Component, Watch } from 'vue-property-decorator'
 import { mapActions, mapGetters, mapMutations, mapState } from 'vuex'
 import locales from './locales'
 import DataSourceBar from '../../../common/DataSourceBar'
-import { handleSuccess, handleError, loadingBox, kylinMessage, kylinConfirm } from 'util/business'
-import { isIE, isFireFox, groupData, objectClone, filterObjectArray, handleSuccessAsync, indexOfObjWithSomeKey } from 'util'
+import { handleSuccess, handleError, loadingBox, kylinMessage, kylinConfirm } from '../../../../util/business'
+import { isIE, groupData, objectClone, filterObjectArray, handleSuccessAsync, indexOfObjWithSomeKey, debounceEvent } from '../../../../util'
 import $ from 'jquery'
 import DimensionModal from '../DimensionsModal/index.vue'
 import BatchMeasureModal from '../BatchMeasureModal/index.vue'
@@ -487,16 +476,68 @@ import DragBar from './dragbar.vue'
 import AddCC from '../AddCCModal/addcc.vue'
 import ShowCC from '../ShowCC/showcc.vue'
 import NModel from './model.js'
-import ActionUpdateGuide from '../../../guide/modelEditPage/ActionUpdateGuide.vue'
+import ActionUpdateGuide from '../../../guide/modelEditPage/ActionUpdateGuide'
+import ModelTitleDescription from '../ModelList/Components/ModelTitleDescription'
+import ModelNavigationTools from '../../../common/ModelTools/ModelNavigationTools'
 import { modelRenderConfig, modelErrorMsg } from './config'
 import { NamedRegex, columnTypeIcon } from '../../../../config'
 @Component({
-  props: ['extraoption'],
+  beforeRouteEnter (to, from, next) {
+    next(vm => {
+      vm.fromRoute = from
+      vm.extraoption = {
+        project: vm.currentSelectedProject,
+        modelName: to.params.modelName,
+        action: to.params.action
+      }
+      // 在添加模型页面刷新,跳转到列表页面
+      if (to.name === 'ModelEdit' && to.params.action === 'add' && from.name === null) {
+        vm.$router.replace({name: 'ModelList', params: { ignoreIntercept: true }})
+        return
+      }
+      vm.initEditModel()
+    })
+  },
+  beforeRouteLeave (to, from, next) {
+    if (this.$store.state.config.platform === 'iframe') {
+      next()
+    } else {
+      if (!to.params.ignoreIntercept) {
+        next(false)
+        setTimeout(() => {
+          this.$confirm(this.$t('kylinLang.common.willGo'), this.$t('kylinLang.common.notice'), {
+            confirmButtonText: this.$t('discardChange'),
+            cancelButtonText: this.$t('continueEditing'),
+            type: 'warning'
+          }).then(() => {
+            if (to.name === 'refresh') { // 刷新逻辑下要手动重定向
+              next()
+              this.$nextTick(() => {
+                this.$router.replace({name: 'ModelList', params: { refresh: true }})
+              })
+              return
+            }
+            next()
+          }).catch(() => {
+            if (to.name === 'refresh') { // 取消刷新逻辑,所有上一个project相关的要撤回
+              let preProject = cacheSessionStorage('preProjectName') // 恢复上一次的project
+              this.setProject(preProject)
+              this.getUserAccess({project: preProject})
+            }
+            next(false)
+          })
+        })
+      } else {
+        next()
+      }
+    }
+  },
   computed: {
     ...mapGetters([
       'currentSelectedProject',
       'isFullScreen',
-      'datasourceActions'
+      'datasourceActions',
+      'supportUrl'
     ]),
     ...mapState('TableJoinModal', {
       tableJoinDialogShow: state => state.isShow
@@ -568,11 +609,15 @@ import { NamedRegex, columnTypeIcon } from '../../../../config'
     ModelSaveConfig,
     AddCC,
     ShowCC,
-    ActionUpdateGuide
+    ActionUpdateGuide,
+    ModelTitleDescription,
+    ModelNavigationTools
   },
   locales
 })
 export default class ModelEdit extends Vue {
+  extraoption = null
+  fromRoute = null
   datasource = []
   modelRender = {tables: {}}
   dimensionSelectedList = []
@@ -588,7 +633,7 @@ export default class ModelEdit extends Vue {
   modelGlobalSearch = '' // model全局搜索信息
   showSearchResult = true
   modelGlobalSearchResult = []
-  modelData = {}
+  modelData = null
   columnTypeIconMap = columnTypeIcon
   modelSearchActionSuccessTip = ''
   modelSearchActionHistoryList = []
@@ -648,6 +693,12 @@ export default class ModelEdit extends Vue {
   currentPot = { x: 0, y: 0 }
   linkLineFocus = []
   showModelGuide = false
+  saveBtnLoading = false
+  showOnlyConnectedColumn = false
+  debounceTimer = {
+    tableType: null,
+    expand: null
+  }
   get disableDelDimTips () {
     if (this.isHybridModel) {
       return this.$t('streamTips')
@@ -658,26 +709,77 @@ export default class ModelEdit extends Vue {
   get isHybridModel () {
     return this.modelInstance.getFactTable() && this.modelInstance.getFactTable().batch_table_identity || this.modelInstance.model_type === 'HYBRID'
   }
+  get allDimension () {
+    return this.modelRender.dimensions.slice(0, this.boardPager.dimension.pageOffset * this.boardPager.dimension.pageSize) || []
+  }
+  get allMeasure () {
+    return this.modelRender.all_measures.slice(0, this.boardPager.measure.pageOffset * this.boardPager.measure.pageSize) || []
+  }
+  get canDelMeasureAll () { // 控制批量删除按钮的 disable 状态
+    let flag = true // 默认不可点
+    if (this.modelRender.all_measures.length === 0) {
+      flag = true
+    } else {
+      let temp = this.modelRender.all_measures.filter((item) => {
+        return item.name === 'COUNT_ALL'
+      })
+      if (temp.length > 0) { // 如果有count all 这个度量,则批量删除按钮不可用
+        flag = this.modelRender.all_measures.length === temp.length
+      } else {
+        flag = this.modelRender.all_measures.length === 0
+      }
+    }
+    return flag
+  }
+  get toggleMeasureStatus () { // 控制批量删除度量的全选切换按钮的状态
+    let temp = this.modelRender.all_measures.filter((item) => {
+      return item.name === 'COUNT_ALL'
+    })
+    if (temp.length > 0) { // 如果有count all 全选文案的切换要去掉count all 后
+      return this.modelRender.all_measures.length - 1
+    } else {
+      return this.modelRender.all_measures.length
+    }
+  }
+  get isSchemaBrokenModel () {
+    return this.modelRender.broken_reason === 'SCHEMA'
+  }
+  get isDisableBatchCheck () {
+    if (this.allDimension.length === 1 && (this.modelInstance.second_storage_enabled || this.isHybridModel) && this.modelInstance.partition_desc && this.modelInstance.partition_desc.partition_date_column === this.allDimension[0].column) {
+      return true
+    } else {
+      return false
+    }
+  }
+  get tableBoxStyleNoZoom () {
+    return (drawSize) => {
+      if (drawSize) {
+        // let zoom = this.modelRender.zoom / 10
+        return {'z-index': drawSize.zIndex, width: drawSize.width + 'px', height: drawSize.height + 'px', left: drawSize.left + 'px', top: drawSize.top + 'px'}
+      }
+    }
+  }
+  get searchResultData () {
+    return groupData(this.modelGlobalSearchResult, 'kind')
+  }
   // 是否展示缺失事实表提醒
   get showFactTableAlert () {
     return Object.values(this.modelRender.tables).filter(table => table.kind === 'FACT').length > 0
   }
-  // 维度、度量滚动到底部
-  boardScrollBottom (name) {
-    switch (name) {
-      case 'dimension':
-        const { dimension } = this.boardPager
-        const dimensionTotalSize = this.modelRender.dimensions.length
-        if (dimension.pageOffset <= Math.ceil(dimensionTotalSize / dimension.pageSize)) {
-          this.$set(dimension, 'pageOffset', dimension.pageOffset + 1)
-        }
-      case 'measure':
-        const { measure } = this.boardPager
-        const measureTotalSize = this.modelRender.all_measures.length
-        if (measure.pageOffset <= Math.ceil(measureTotalSize / measure.pageSize)) {
-          this.$set(measure, 'pageOffset', measure.pageOffset + 1)
-        }
-    }
+  // 画布位置调整
+  get getModelEditStyles () {
+    if (!this.modelRender || !this.modelRender.marginClient) return {marginLeft: 0, marginTop: 0}
+    const { left, top } = this.modelRender.marginClient
+    return {marginLeft: `${left}px`, marginTop: `${top}px`}
+  }
+  // 获取当前展示的列
+  getCurrentColumns (t, type) {
+    const columns = type === 'cc' ? t.computed_columns : t.showColumns
+    return this.showOnlyConnectedColumn ? columns.filter(it => it.isPK || it.isFK) : columns
+  }
+  @Watch('modelGlobalSearch')
+  watchSearch (v) {
+    this.showSearchResult = v
   }
   // 当维度或度量高度改变时,需要增加页码以填充多余的空白区域
   changePageOfBround (obj) {
@@ -704,14 +806,18 @@ export default class ModelEdit extends Vue {
   }
   // 双击表头 - 展开或收起
   handleDBClick (t) {
-    this.$set(t, 'spreadOut', !t.spreadOut)
-    !t.spreadOut && this.$set(t, 'spreadHeight', t.drawSize.height)
-    const boxH = !t.spreadOut ? this.$el.querySelector('.table-title').offsetHeight + 4 : t.spreadHeight
-    this.$set(t.drawSize, 'height', boxH)
-    // this.$refs[`table_${t.guid}`].length && (this.$refs[`table_${t.guid}`][0].style.cssText += `height: ${boxH}px;`)
-    this.$nextTick(() => {
-      this.modelInstance.plumbTool.refreshPlumbInstance()
-    })
+    if (this.debounceTimer.expand) {
+      clearTimeout(this.debounceTimer.expand)
+    }
+    this.debounceTimer.expand = setTimeout(() => {
+      const boxH = t.spreadOut ? this.$el.querySelector('.table-title').offsetHeight + 4 : t.spreadHeight
+      this.$set(t, 'spreadHeight', t.drawSize.height)
+      this.$set(t, 'spreadOut', !t.spreadOut)
+      this.$set(t.drawSize, 'height', boxH)
+      this.$nextTick(() => {
+        this.modelInstance.plumbTool.refreshPlumbInstance()
+      })
+    }, 300)
   }
   // 滚动加载 columns
   handleScrollBottom (table) {
@@ -820,6 +926,7 @@ export default class ModelEdit extends Vue {
       }
     })
     this.linkLineFocus.splice(0, this.linkLineFocus.length)
+    this.removeCustomHoverStatus()
   }
   handleMouseLeave (event, table, column) {
     this.cancelLinkLineFocus()
@@ -852,6 +959,8 @@ export default class ModelEdit extends Vue {
       path.setAttribute('stroke', '#0875DA')
       path.setAttribute('stroke-width', 2)
       path.setAttribute('fill', 'none')
+      // svg.appendChild(path)
+      // svg.innerHTML = path
       svg.appendChild(path)
       modelEdit.appendChild(svg)
     } else {
@@ -911,10 +1020,14 @@ export default class ModelEdit extends Vue {
       }
     }
   }
-  async showGuide () {
-    this.showModelGuide = true
+  async showFistAddModelGuide () {
+    await this.callGuideModal({ isShowDimAndMeasGuide: true })
     localStorage.setItem('isFirstAddModel', 'false')
   }
+  async showUpdateGuide () {
+    this.showModelGuide = true
+    localStorage.setItem('isFirstUpdateModel', 'false')
+  }
   initAllPanels () {
     if (!this.isSchemaBrokenModel) {
       this.panelAppear.dimension.display = true
@@ -933,44 +1046,6 @@ export default class ModelEdit extends Vue {
     }
     this.panelAppear.brokenFocus.icon_display = true
   }
-  get allDimension () {
-    return this.modelRender.dimensions.slice(0, this.boardPager.dimension.pageOffset * this.boardPager.dimension.pageSize) || []
-  }
-  get allMeasure () {
-    return this.modelRender.all_measures.slice(0, this.boardPager.measure.pageOffset * this.boardPager.measure.pageSize) || []
-  }
-  get canDelMeasureAll () { // 控制批量删除按钮的 disable 状态
-    let flag = true // 默认不可点
-    if (this.modelRender.all_measures.length === 0) {
-      flag = true
-    } else {
-      let temp = this.modelRender.all_measures.filter((item) => {
-        return item.name === 'COUNT_ALL'
-      })
-      if (temp.length > 0) { // 如果有count all 这个度量,则批量删除按钮不可用
-        flag = this.modelRender.all_measures.length === temp.length
-      } else {
-        flag = this.modelRender.all_measures.length === 0
-      }
-    }
-    return flag
-  }
-  get toggleMeasureStatus () { // 控制批量删除度量的全选切换按钮的状态
-    let temp = this.modelRender.all_measures.filter((item) => {
-      return item.name === 'COUNT_ALL'
-    })
-    if (temp.length > 0) { // 如果有count all 全选文案的切换要去掉count all 后
-      return this.modelRender.all_measures.length - 1
-    } else {
-      return this.modelRender.all_measures.length
-    }
-  }
-  query (className) {
-    return $(this.$el.querySelector(className))
-  }
-  get isSchemaBrokenModel () {
-    return this.modelRender.broken_reason === 'SCHEMA'
-  }
   // 定位含有broken连线的table
   focusBrokenLinkedTable () {
     if (this.modelInstance) {
@@ -993,45 +1068,6 @@ export default class ModelEdit extends Vue {
       }
     }
   }
-  guideActions (obj) {
-    let data = obj.data
-    if (obj.action === 'addTable') {
-      let { left, top } = this.modelInstance.renderDom.getBoundingClientRect()
-      this.modelInstance.addTable({
-        table: data.tableName,
-        alias: data.tableName.split('.')[1],
-        guid: data.guid,
-        isSecStorageEnabled: this.modelInstance.second_storage_enabled,
-        drawSize: {
-          left: data.x - left - this.modelRender.zoomXSpace,
-          top: data.y - top - this.modelRender.zoomYSpace
-        }
-      })
-    } else if (obj.action === 'link') {
-      let fTable = this.modelInstance.getTableByGuid(data.fguid)
-      let pTable = this.modelInstance.getTableByGuid(data.pguid)
-      let joinDialogOption = {
-        fid: data.fguid,
-        pid: data.pguid,
-        joinType: data.joinType,
-        fColumnName: fTable.alias + '.' + data.fColumnName,
-        pColumnName: pTable.alias + '.' + data.pColumnName,
-        tables: this.modelRender.tables
-      }
-      this.callJoinDialog(joinDialogOption)
-    }
-  }
-  // 取消table编辑
-  cancelTableEdit () {
-    this.showTableCoverDiv = false
-    this.currentEditTable = null
-    this.showEditAliasForm = false
-    this.formTableAlias.currentEditAlias = ''
-    this.delTipVisible = false
-  }
-  showDelTableTip () {
-    this.delTipVisible = true
-  }
   // 维度、度量滚动到底部
   boardScrollBottom (name) {
     switch (name) {
@@ -1049,25 +1085,6 @@ export default class ModelEdit extends Vue {
         }
     }
   }
-  // 当维度或度量高度改变时,需要增加页码以填充多余的空白区域
-  changePageOfBround (obj) {
-    switch (obj) {
-      case 'dimension':
-        const scrollContent = this.$el.querySelector('.panel-dimension .content-scroll-layout')
-        const dimensionDom = this.$el.querySelector('.panel-dimension .dimension-list')
-        if (!scrollContent || !dimensionDom) return
-        if (dimensionDom.offsetHeight < scrollContent.offsetHeight) {
-          this.boardScrollBottom('dimension')
-        }
-      case 'measure':
-        const measureScrollContent = this.$el.querySelector('.panel-measure .content-scroll-layout')
-        const measureDom = this.$el.querySelector('.panel-measure .measure-list')
-        if (!measureScrollContent || !measureDom) return
-        if (measureDom.offsetHeight < measureScrollContent.offsetHeight) {
-          this.boardScrollBottom('measure')
-        }
-    }
-  }
   toggleCheckbox () {
     if (this.allDimension.length === 0 && !this.isShowCheckbox) {
       return
@@ -1076,13 +1093,6 @@ export default class ModelEdit extends Vue {
     }
     this.isShowCheckbox = !this.isShowCheckbox
   }
-  get translate () {
-    if (this.isShowCheckbox) {
-      return 0 - this.panelAppear.dimension.width
-    } else {
-      return 0
-    }
-  }
   toggleMeaCheckbox () {
     if (this.modelRender.all_measures.length === 1 && !this.isShowMeaCheckbox) {
       return
@@ -1091,13 +1101,6 @@ export default class ModelEdit extends Vue {
     }
     this.isShowMeaCheckbox = !this.isShowMeaCheckbox
   }
-  get translateMea () {
-    if (this.isShowMeaCheckbox) {
-      return 0 - this.panelAppear.measure.width
-    } else {
-      return 0
-    }
-  }
   toggleCCCheckbox () {
     if (this.modelRender.computed_columns.length === 0 && !this.isShowCCCheckbox) {
       return
@@ -1106,25 +1109,19 @@ export default class ModelEdit extends Vue {
     }
     this.isShowCCCheckbox = !this.isShowCCCheckbox
   }
-  get translateCC () {
-    if (this.isShowCCCheckbox) {
-      return 0 - this.panelAppear.cc.width
-    } else {
-      return 0
-    }
-  }
   async delTable (table) {
+    this.blurTableActionDropdown()
     if (!this.modelInstance.checkTableCanDel(table.guid)) {
       await this.$msgbox({
         title: this.$t('kylinLang.common.delete'),
         message: this.$t('delTableTip'),
         type: 'warning',
         showCancelButton: true,
-        confirmButtonText: this.$t('kylinLang.common.delete')
+        confirmButtonText: this.$t('kylinLang.common.delete'),
+        closeOnClickModal: false
       })
     }
     this.modelInstance.delTable(table.guid).then(() => {
-      // this.cancelTableEdit()
       if (this.modelData.available_indexes_count > 0 && !this.isIgnore) {
         this.showChangeTips()
       }
@@ -1181,6 +1178,16 @@ export default class ModelEdit extends Vue {
         item.setAttribute('class', `${item.className.baseVal.replace(/is-focus|is-broken/g, '')}`)
       })
     }
+    this.blurTableActionDropdown()
+    document.activeElement && document.activeElement.blur()
+  }
+  // 隐藏下拉框
+  blurTableActionDropdown () {
+    if (this.$refs.tableActionsDropdown) {
+      this.$refs.tableActionsDropdown.forEach(it => {
+        it.visible && it.hide()
+      })
+    }
   }
   closeAddMeasureDia ({isSubmit, data, isEdit, fromSearch}) {
     if (isSubmit) {
@@ -1191,33 +1198,30 @@ export default class ModelEdit extends Vue {
     }
     this.measureVisible = false
   }
-  async changeTableType (t) {
-    if (t.kind === 'FACT' && t.source_type === 1) {
-      return
+  changeTableType (t) {
+    if (this.debounceTimer.tableType) {
+      clearTimeout(this.debounceTimer.tableType)
     }
-    if (this._checkTableType(t)) {
-      let joinT = Object.keys(this.modelInstance.linkUsedColumns).filter(it => it.indexOf(t.guid) === 0)
-      if (joinT.length && joinT.some(it => this.modelInstance.linkUsedColumns[it].length)) {
-        // this.cancelTableEdit()
-        this.$message({
-          message: this.$t('changeTableJoinCondition'),
-          type: 'warning'
-        })
+    this.debounceTimer.tableType = setTimeout(() => {
+      if (t.kind === 'FACT' && t.source_type === 1) {
         return
       }
-      t.kind !== 'FACT' && await this.$msgbox({
-        title: t.kind !== 'FACT' ? this.$t('switchFact') : this.$t('switchLookup'),
-        type: 'warning',
-        message: this.$t('switchTableTypeTips'),
-        showCancelButton: true,
-        confirmButtonText: this.$t('switchReplace')
-      })
-      this.modelInstance.changeTableType(t)
-      // this.cancelTableEdit()
-      if (this.modelData.available_indexes_count > 0 && !this.isIgnore) {
-        this.showChangeTips()
+      if (this._checkTableType(t)) {
+        let joinT = Object.keys(this.modelInstance.linkUsedColumns).filter(it => it.indexOf(t.guid) === 0)
+        if (joinT.length && joinT.some(it => this.modelInstance.linkUsedColumns[it].length)) {
+          this.$message({
+            message: this.$t('changeTableJoinCondition'),
+            type: 'warning'
+          })
+          return
+        }
+        this.modelInstance.changeTableType(t)
+        if (this.modelData.available_indexes_count > 0 && !this.isIgnore) {
+          this.showChangeTips()
+        }
+        this.blurTableActionDropdown()
       }
-    }
+    }, 300)
   }
   _checkTableType (t) {
     if (t.fact) {
@@ -1242,6 +1246,42 @@ export default class ModelEdit extends Vue {
   autoLayout () {
     this.modelInstance.renderPosition()
   }
+  // 额外功能
+  handleActionsCommand (command, showOnlyConnectedColumn) {
+    this.showOnlyConnectedColumn = showOnlyConnectedColumn
+    if (command === 'collapseAllTables') {
+      const tableTitleHeight = document.querySelector('.table-title').offsetHeight
+      for (let item in this.modelRender.tables) {
+        this.$set(this.modelRender.tables[item], 'spreadOut', false)
+        this.$set(this.modelRender.tables[item], 'spreadHeight', this.modelRender.tables[item].drawSize.height < 140 ? modelRenderConfig.tableBoxHeight : this.modelRender.tables[item].drawSize.height)
+        this.$set(this.modelRender.tables[item].drawSize, 'height', tableTitleHeight + 4)
+      }
+    } else if (command === 'expandAllTables') {
+      for (let item in this.modelRender.tables) {
+        this.$set(this.modelRender.tables[item], 'spreadOut', true)
+        this.$set(this.modelRender.tables[item].drawSize, 'height', modelRenderConfig.tableBoxHeight)
+        this.$set(this.modelRender.tables[item], 'spreadHeight', modelRenderConfig.tableBoxHeight)
+      }
+    } else if (command === 'showOnlyConnectedColumn') {
+      for (let item in this.modelRender.tables) {
+        const { columns } = this.modelRender.tables[item]
+        const len = columns.filter(it => it.isFK || it.isPK).length
+        const columnHeight = document.querySelector('.column-li').offsetHeight
+        const tableTitleHeight = document.querySelector('.table-title').offsetHeight
+        const sumHeight = columnHeight * len
+        if (sumHeight !== 0) {
+          this.$set(this.modelRender.tables[item], 'spreadOut', true)
+          this.$set(this.modelRender.tables[item].drawSize, 'height', tableTitleHeight + sumHeight + 4)
+        } else {
+          this.$set(this.modelRender.tables[item], 'spreadOut', false)
+          this.$set(this.modelRender.tables[item].drawSize, 'height', tableTitleHeight + 4)
+        }
+      }
+    }
+    this.$nextTick(() => {
+      this.modelInstance.plumbTool.refreshPlumbInstance()
+    })
+  }
   async initModelDesc (cb) {
     if (this.extraoption.modelName && this.extraoption.action === 'edit') {
       this.getModelByModelName({model_name: this.extraoption.modelName, project: this.extraoption.project}).then((response) => {
@@ -1319,13 +1359,6 @@ export default class ModelEdit extends Vue {
     }
     this.modelInstance.delDimension(d.name)
   }
-  get isDisableBatchCheck () {
-    if (this.allDimension.length === 1 && (this.modelInstance.second_storage_enabled || this.isHybridModel) && this.modelInstance.partition_desc && this.modelInstance.partition_desc.partition_date_column === this.allDimension[0].column) {
-      return true
-    } else {
-      return false
-    }
-  }
   toggleCheckAllDimension () {
     if (this.dimensionSelectedList.length === this.modelRender.dimensions.length || (this.modelInstance.second_storage_enabled || this.isHybridModel) && this.dimensionSelectedList.length + 1 === this.modelRender.dimensions.length) {
       this.dimensionSelectedList = []
@@ -1383,9 +1416,6 @@ export default class ModelEdit extends Vue {
     this.modelInstance.delCC(name)
   }
   editCC (cc) {
-    // this.showCCDetailDialog({
-    //   ccDetail: cc
-    // })
     this.showAddCCDialog({
       modelInstance: this.modelInstance,
       ccForm: cc,
@@ -1422,7 +1452,7 @@ export default class ModelEdit extends Vue {
     this.showEditAliasForm = false
   }
   openEditAliasForm (table) {
-    // this.showEditAliasForm = true
+    this.blurTableActionDropdown()
     this.$prompt(null, this.$t('rename'), {
       type: 'info',
       inputValue: table.alias,
@@ -1430,9 +1460,9 @@ export default class ModelEdit extends Vue {
       inputErrorMessage: this.$t('kylinLang.common.nameFormatValidTip'),
       inputPlaceholder: this.$t('kylinLang.common.pleaseInput'),
       confirmButtonText: this.$t('kylinLang.common.save'),
-      cancelButtonText: this.$t('kylinLang.common.cancel')
+      cancelButtonText: this.$t('kylinLang.common.cancel'),
+      closeOnClickModal: false
     }).then(({ value }) => {
-      // table.alias = value
       this.saveNewAlias(table, value)
     })
   }
@@ -1441,23 +1471,14 @@ export default class ModelEdit extends Vue {
     columns.forEach((col) => {
       this.$set(col, 'isHidden', filterVal ? !reg.test(col.name) : false)
     })
-    // t.columns = filterObjectArray(columns, 'isfiltered', true)
   }
   getFilteredColumns (columns) {
     return filterObjectArray(columns, 'isHidden', false)
   }
   // 拖动画布
   dragBox (x, y, boxW, boxH, info, oDiv) {
-    const child = oDiv.querySelector('.model-edit')
-    if (child) {
-      const mL = child.offsetLeft ?? 0
-      const mT = child.offsetTop ?? 0
-      child.style.cssText += `margin-left: ${mL + x}px; margin-top: ${mT + y}px`
-    }
-    this.$nextTick(() => {
-      this.modelInstance.getSysInfo()
-      this.modelInstance.moveModelPosition(x, y)
-    })
+    this.modelInstance.getSysInfo()
+    this.modelInstance.moveModelPosition(x, y)
   }
   // 拖动tree-table
   dragTable (node) {
@@ -1476,7 +1497,7 @@ export default class ModelEdit extends Vue {
     }
     this.currentDragColumnData = {
       guid: table.guid,
-      columnName: col.name,
+      columnName: col.name || col.columnName,
       btype: col.btype
     }
     return true
@@ -1487,10 +1508,7 @@ export default class ModelEdit extends Vue {
       return
     }
     var target = event.currentTarget
-    // const dom = document.getElementById(`${t.guid}_${col.column}`)
     $(target).addClass('drag-column-li-in')
-    // if (dom.className.indexOf('drag-column-in') >= 0) return
-    // dom.className += ' drag-column-li-in'
   }
   dragColumnLeave (event) {
     var target = event.currentTarget
@@ -1535,7 +1553,6 @@ export default class ModelEdit extends Vue {
     this.removeDragInClass()
     this.$nextTick(() => {
       if (Object.keys(this.modelInstance.tables).length === 1 && !(!currentTablesIncludeFact.length && tableIsFact)) {
-        // this.editTable(Object.keys(this.modelInstance.tables)[0])
         const guid = Object.keys(this.modelInstance.tables)[0]
         this.$refs[`table_${guid}`]?.[0].querySelector('.table-dropdown .setting-icon')?.click()
       }
@@ -1731,8 +1748,9 @@ export default class ModelEdit extends Vue {
           t._cache_search_columns = t.columns
           t.showColumns = [...pfkLinkColumns, ...pkLinkColumns, ...fkLinkColumns, ...unlinkColumns].slice(0, t.columnPerSize)
         })
-        // this.$set(this.modelRender, 'tables', cloneTables)
         this.$set(this.modelInstance, 'tables', this.modelRender.tables)
+        console.log(this.modelInstance.allConnInfo)
+        const [currentConnector] = Object.values(this.modelInstance.allConnInfo).slice(-1)
       }, 500)
     }
     // 同步因为预计算被禁用的表
@@ -1761,7 +1779,6 @@ export default class ModelEdit extends Vue {
       if (this.currentDragColumnData.guid === guid) {
         return
       }
-      // $(target).parents('.column-list-box').addClass('drag-in')
     }
   }
   allowDropColumnToPanle (e) {
@@ -1815,6 +1832,7 @@ export default class ModelEdit extends Vue {
   selectResult (e, select) {
     this.modelSearchActionSuccessTip = ''
     this.searchHandleStart = true
+    this.modelGlobalSearch = ''
     var moreInfo = select.more
     if (select.action === 'showtable') {
       if (select.more) {
@@ -1939,29 +1957,14 @@ export default class ModelEdit extends Vue {
     }
     this.panelAppear.search.display = false
   }
-  @Watch('dimensionDialogShow')
-  @Watch('singleDimensionDialogShow')
-  @Watch('tableJoinDialogShow')
-  @Watch('measureVisible')
-  tableJoinDialogClose (val) {
-    if (!val) {
-      if (this.searchHandleStart) {
-        this.searchHandleStart = false
-        this.panelAppear.search.display = true
-      }
-    }
-  }
   searchModelEverything (val) {
     this.modelGlobalSearchResult = this.modelInstance.search(val)
+    console.log(this.modelSearchActionHistoryList, 2222)
   }
   getColumnType (tableName, column) {
     var ntable = this.modelInstance.getTable('alias', tableName)
     return ntable && ntable.getColumnType(column)
   }
-  @Watch('modelGlobalSearch')
-  watchSearch (v) {
-    this.showSearchResult = v
-  }
   panelStyle (k) {
     // return (k) => {
     if (['dimension', 'measure'].includes(k)) {
@@ -1980,28 +1983,6 @@ export default class ModelEdit extends Vue {
   tableBoxStyle (drawSize) {
     return {'z-index': drawSize.zIndex, width: drawSize.width + 'px', height: drawSize.height + 'px', left: drawSize.left + 'px', top: drawSize.top + 'px'}
   }
-  get tableBoxStyleNoZoom () {
-    return (drawSize) => {
-      if (drawSize) {
-        // let zoom = this.modelRender.zoom / 10
-        return {'z-index': drawSize.zIndex, width: drawSize.width + 'px', height: drawSize.height + 'px', left: drawSize.left + 'px', top: drawSize.top + 'px'}
-      }
-    }
-  }
-  // get tableBoxToolStyleNoZoom () {
-  //   return (drawSize) => {
-  //     if (drawSize) {
-  //       let zoom = this.modelRender.zoom / 10
-  //       if (drawSize.isInRightEdge) {
-  //         return {left: drawSize.left - 230 + 'px', top: drawSize.top + 'px'}
-  //       }
-  //       return {left: this.currentEditTable.drawSize.width + drawSize.left + 'px', top: drawSize.top + 'px'}
-  //     }
-  //   }
-  // }
-  get searchResultData () {
-    return groupData(this.modelGlobalSearchResult, 'kind')
-  }
   // 判断是否添加分区列方法
   addPartitionFunc (data) {
     data.available_indexes_count = this.modelData.available_indexes_count
@@ -2020,7 +2001,7 @@ export default class ModelEdit extends Vue {
         if (res.isSubmit) {
           this.handleSaveModel({data, modelSaveConfigData: res.data, createBaseIndex: this.modelInstance.has_base_table_index && this.modelInstance.has_base_agg_index ? false : res.with_base_index})
         } else {
-          this.$emit('saveRequestEnd')
+          this.saveBtnLoading = false
         }
       })
     } else {
@@ -2035,7 +2016,7 @@ export default class ModelEdit extends Vue {
           await this.checkMeasureWithCC(data)
           this.addPartitionFunc(data)
         }).catch(() => {
-          this.$emit('saveRequestEnd')
+          this.saveBtnLoading = false
         })
       } else {
         await this.checkMeasureWithCC(data)
@@ -2046,14 +2027,14 @@ export default class ModelEdit extends Vue {
         this._tipHasAloneTable(err.aloneCount).then(() => {
           this.generateModelData(true)
         }).catch(() => {
-          this.$emit('saveRequestEnd')
+          this.saveBtnLoading = false
         })
       } else {
         kylinMessage(this.$t(modelErrorMsg[err.errorKey], {tableName: err.tableName}), {type: 'warning'})
       }
-      this.$emit('saveRequestEnd')
+      this.saveBtnLoading = false
     }).catch(() => {
-      this.$emit('saveRequestEnd')
+      this.saveBtnLoading = false
     })
   }
   // 对于老数据检测 SUM 或 PERCENTILE_APPROX 度量中是否使用 varchar cc 列
@@ -2083,21 +2064,29 @@ export default class ModelEdit extends Vue {
       }
     })
   }
-  setModelBoundStyle () {
-    const { left, top } = this.modelRender.marginClient ?? {left: 0, top: 0}
-    const dom = this.$el.querySelector('.model-edit')
-    if (!dom) return
-    dom.style.cssText += `margin-left: ${left}px; margin-top: ${top}px`
+  // 保存模型
+  saveModelEvent () {
+    this.saveBtnLoading = true
+    this.generateModelData()
+  }
+  // 返回上级页面
+  goModelList () {
+    this.toggleFullScreen(false)
+    if (this.fromRoute && this.fromRoute.name) {
+      this.$router.push({name: this.fromRoute.name, params: {modelName: this.currentModel}})
+      return
+    }
+    this.$router.push({name: 'ModelList'})
   }
-  async mounted () {
+  async initEditModel () {
     this.globalLoading.show()
     this.$el.onselectstart = function (e) {
       return false
     }
     // 注册保存事件
-    this.$on('saveModel', () => {
-      this.generateModelData()
-    })
+    // this.$on('saveModel', () => {
+    //   this.generateModelData()
+    // })
     this.clearDatasourceCache(this.currentSelectedProject) // 清空 当前project下的 datasource缓存
     // 如果是 edit 需要获取模型使用的 table 信息
     try {
@@ -2150,6 +2139,7 @@ export default class ModelEdit extends Vue {
         })
         this.$nextTick(() => {
           this.checkInvalidIndex()
+          this.exchangeModelTable()
         })
       } catch (e) {
         this.globalLoading.hide()
@@ -2162,10 +2152,30 @@ export default class ModelEdit extends Vue {
         })
       }
     })
-    
-    this.setModelBoundStyle()
-    if (localStorage.getItem('isFirstAddModel') === 'true' || !localStorage.getItem('isFirstAddModel')) {
-      await this.showGuide()
+    if (localStorage.getItem('isFirstAddModel') === 'true') {
+      await this.showFistAddModelGuide()
+    }
+    const keVersion = this.$store.state.system.serverAboutKap['ke.version']?.match(/Kyligence Enterprise (\d+.\d+.\d+.\d+)-\w+/)[1]
+    if ((localStorage.getItem('isFirstUpdateModel') === 'true' || !localStorage.getItem('isFirstUpdateModel')) && (keVersion && +keVersion.split('.').join('') >= 45160) && localStorage.getItem('isFirstAddModel') === 'false') {
+      await this.showUpdateGuide()
+    }
+  }
+  // table drawSize 数据更新
+  exchangeModelTable () {
+    if (!Object.values(this.modelRender.tables).length) return
+    const modelTableTitle = this.$el.querySelector('.table-title')
+    const modelTableBoxBorder = +window.getComputedStyle(modelTableTitle)['borderWidth'].replace(/px/, '')
+    for (let item in this.modelRender.tables) {
+      const { drawSize } = this.modelRender.tables[item]
+      if (drawSize.height === modelTableTitle.offsetHeight + modelTableBoxBorder * 2 + 4) {
+        this.modelRender.tables[item].spreadOut = false
+        this.modelRender.tables[item].spreadHeight = modelRenderConfig.tableBoxHeight
+      } else if (drawSize.height < 140) {
+        this.$set(this.modelRender.tables[item].drawSize, 'height', 140)
+        this.modelRender.tables[item].spreadHeight = modelRenderConfig.tableBoxHeight
+      } else {
+        this.modelRender.tables[item].spreadHeight = this.modelRender.tables[item].drawSize.height
+      }
     }
   }
   // 更新 model.js 里的 tables 数据
@@ -2179,6 +2189,11 @@ export default class ModelEdit extends Vue {
   async checkInvalidIndex () {
     if (this.extraoption.action === 'edit') {
       const res = await this.modelInstance.generateMetadata(true)
+      // const _data = {
+      //   project: this.currentSelectedProject,
+      //   model_id: res.uuid,
+      //   join_tables: res.join_tables
+      // }
       const response = await this.invalidIndexes(res)
       const result = await handleSuccessAsync(response)
       const { computed_columns, anti_flatten_lookups } = result
@@ -2315,21 +2330,11 @@ export default class ModelEdit extends Vue {
           } else {
             this.gotoIndexdialogVisible = true
           }
-          // kylinConfirm(this.$t('saveSuccessTip'), {
-          //   confirmButtonText: this.$t('addIndexTip'),
-          //   cancelButtonText: this.$t('ignoreaddIndexTip'),
-          //   type: 'success',
-          //   confirmButtonClass: 'guide-gotoindex-btn'
-          // }, this.$t('addIndexTip')).then(() => {
-          //   this.$router.replace({name: 'ModelList', params: { ignoreIntercept: true, addIndex: true }})
-          // }).catch(() => {
-          //   this.$router.replace({name: 'ModelList', params: { ignoreIntercept: true }})
-          // })
-          this.$emit('saveRequestEnd')
+          this.saveBtnLoading = false
         }, 1000)
       })
     }).catch((res) => {
-      this.$emit('saveRequestEnd')
+      this.saveBtnLoading = false
       handleError(res)
     })
   }
@@ -2391,14 +2396,14 @@ export default class ModelEdit extends Vue {
   z-index:100001 !important;
 }
 .jtk-connector.is-focus {
-  path {
-    stroke: @ke-color-primary;
+  path:not(#use) {
+    stroke: @ke-color-primary !important;
     stroke-width: 2;
   }
 }
 .jtk-connector.is-broken.is-focus {
-  path {
-    stroke: @ke-color-danger;
+  path:not(#use) {
+    stroke: @ke-color-danger !important;
     stroke-width: 2;
   }
 }
@@ -2407,7 +2412,7 @@ export default class ModelEdit extends Vue {
   cursor: pointer;
   border-radius: 10px;
   text-align: center;
-  background: @grey-3;
+  background: @ke-background-color-secondary;
   padding: 0 10px;
   &.is-hide {
     font-size: 0;
@@ -2420,7 +2425,6 @@ export default class ModelEdit extends Vue {
     }
   }
   &.is-hide.jtk-hover {
-    // background: #0875DA;
     .join-type {
       display: inline-block;
     }
@@ -2458,14 +2462,12 @@ export default class ModelEdit extends Vue {
       background: @ke-color-danger;
     }
   }
-  &.jtk-hover {
-    .close-icon {
-      display: inline-block;
-    }
-  }
   &.jtk-hover:not(.link-label-broken):not(.is-focus) {
     .join-type {
-      color: #9DCEFB;
+      color: @base-color-6;
+      &:hover {
+        color: @ke-color-primary;
+      }
     }
     
   }
@@ -2476,6 +2478,13 @@ export default class ModelEdit extends Vue {
     border-radius: 100%;
     background: @text-placeholder-color;
   }
+  .line-label-bar {
+    &:hover {
+      .close-icon {
+        display: inline-block;
+      }
+    }
+  }
   .join-type {
     color: @text-placeholder-color;
     font-size: 30px;
@@ -2491,19 +2500,9 @@ export default class ModelEdit extends Vue {
     right: -13px;
     font-size: 16px;
   }
-  // .close {
-  //   display: none;
-  //   .ky-square-box(14px, 14px);
-  //   line-height: 14px;
-  //   font-size:12px;
-  //   float:right;
-  //   border-radius: 7px;
-  //   margin-left:8px;
-  //   margin-top:3px;
-  // }
 }
 .drag-column-li-in {
-  background-color: #CEE6FD;
+  background-color: @base-color-8;
   border: 2px solid @line-border-drag !important;
 }
 .box-css() {
@@ -2520,12 +2519,178 @@ export default class ModelEdit extends Vue {
 .search-position() {
   position:relative;
 }
+.model-edit-layout {
+  width: 100%;
+  height: 100%;
+  .model-edit-header {
+    width: 100%;
+    height: 72px;
+    display: flex;
+    align-items: center;
+    flex-direction: row;
+    border-bottom: 1px solid @ke-border-secondary;
+    box-sizing: border-box;
+    .model-title {
+      // height: 100%;
+      padding: 0 16px;
+      line-height: 72px;
+      box-sizing: border-box;
+      max-width: 290px;
+      .model-alias-title {
+        max-width: 90%;
+      }
+      .model-alias-label {
+        .filter-status {
+          top: -4px;
+          margin-right: 2px;
+        }
+        .last-modified-tooltip {
+          margin-top: 0;
+        }
+      }
+    }
+    .model-search-layout {
+      flex: 1;
+      position: relative;
+      .el-input {
+        width: 100%;
+        .el-input__inner {
+          border: 0;
+          outline: none;
+          &:focus {
+            border: 1px solid #0875DA;
+            box-shadow: 0px 0px 0px 1px #0875DA;
+          }
+        }
+      }
+      .search-board {
+        width: 100%;
+        position: absolute;
+        // box-shadow: 0 0px 2px 0 @color-text-placeholder;
+        background-color: rgba(255, 255, 255, 1);
+        box-shadow: 0px 2px 8px rgba(50, 73, 107, 0.24);
+        border-radius: 6px;
+        height:calc(~'100vh - 464px')!important;
+        min-height: 250px;
+        z-index: 101;
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        .search-footer {
+          background-color: @ke-border-divider-color;
+          height: 36px;
+          line-height: 36px;
+          text-align: right;
+          padding: 0 8px;
+          box-sizing: border-box;
+          > span {
+            font-size: 12px;
+          }
+          .feedback-btn {
+            color: @ke-color-primary;
+          }
+        }
+      }
+      .el-row {
+        width: 100%;
+        height: 100%;
+        overflow: hidden;
+        padding: 12px 10px 0 10px;
+        box-sizing: border-box;
+        .search-content-col {
+          padding-right: 8px;
+          height: 100%;
+        }
+        .search-history {
+          border-left: 1px solid @ke-border-divider-color;
+          padding-left: 16px;
+          height: 100%;
+        }
+        .search-action-list {
+          font-size:12px;
+          .action-list-title {
+            height:30px;
+            line-height:30px;
+            font-weight: @font-medium;
+            border-bottom: solid 1px @line-split-color;
+          }
+          .search-list-icon {
+            width:16px;
+            height:16px;
+            position:absolute;
+            .ky-square-box(16px, 16px);
+            border-radius: 50%;
+          }
+          .action-content {
+            border-bottom: dashed 1px @line-split-color;
+            .action-title {
+              padding:10px 0;
+            }
+            .action-desc {
+              margin-left:26px;
+              word-break: break-all;
+              i {
+                font-weight: @font-medium;
+                font-style: normal;
+              }
+            }
+          }
+        }
+      }
+      .search-result-box {
+        width: 100%;
+        height: 100%;
+        .search-noresult {
+          font-size:20px;
+          text-align: center;
+          margin-top:100px;
+          color:@text-placeholder-color;
+        }
+        .search-group {
+          padding-top: 5px;
+          padding-bottom: 5px;
+        }
+        .search-content {
+          cursor:pointer;
+          height: 32px;
+          line-height: 32px;
+          padding-left: 8px;
+          &.active,&:hover{
+            background-color:@base-color-9;
+          }
+          .search-category {
+            font-size:12px;
+            color:@text-normal-color;
+          }
+          .search-name {
+            font-size:14px;
+            color:@text-title-color;
+            i {
+              color:@base-color;
+              font-style: normal;
+            }
+          }
+        }
+      }
+    }
+    .model-actions {
+      height: 100%;
+      // width: 300px;
+      display: flex;
+      align-items: center;
+      justify-content: right;
+      padding: 0 16px;
+      box-sizing: border-box;
+    }
+  }
+}
 .model-edit-outer {
-  border-top:@text-placeholder-color;
-  user-select:none;
-  overflow:hidden;
+  border-top: @text-placeholder-color;
+  user-select: none;
+  overflow: hidden;
   .box-css();
-  height: 100%;
+  height: calc(~'100% - 72px');
+  background-color: @ke-background-color-secondary;
   .lose-fact-table-alert {
     position: absolute;
     z-index: 1;
@@ -2550,77 +2715,26 @@ export default class ModelEdit extends Vue {
     z-index: 99999;
     background-color: rgba(24, 32, 36, 0.7);
   }
-  .fast-action-box {
-    width: 210px;
-    left: 215px;
-    color: #fff;
+  .shortcuts-group {
     position: absolute;
-    z-index: 100001;
-    margin-left: 10px;
-    background: #fff;
-    border: 1px solid #ECF0F8;
-    border-radius: 6px;
-    .el-form-item__content {
-      line-height: 0;
-    }
-    &.edge-right {
-      text-align: right;
-      left: -230px;
-      .el-form-item__error {
-        text-align: left;
-      }
-    }
-    div {
-      margin-bottom:5px;
-    }
-    div.alias-form{
-      .el-input {
-        width:140px;
-      }
-      .el-button+.el-button {
-        margin-left:5px;
-        color: @fff;
-      }
-    }
-    div.action {
-      display: inline-block;
-      border-radius: 2px;
-      background:black;
-      color:@fff;
-      height:24px;
-      padding-left:5px;
-      padding-right:6px;
-      font-size:12px;
-      line-height:25px;
-      cursor:pointer;
-      margin-left:0;
-      transform: margin-left ease;
-      &:hover {
-        margin-left: 4px;
-      }
-      &.disabled {
-        opacity: 0.375;
-        cursor: not-allowed;
-      }
-    }
-  }
-  .fast-action-temp-table {
-    z-index:100000!important;
+    right: 20px;
+    bottom: 20px;
   }
   
   .panel-box{
     box-shadow: 0 2px 4px 0 @color-text-placeholder;
     position:relative;
     width:250px;
-    background:#fff;
+    background: @fff;
     position:absolute;
       .panel-title {
         background:@text-normal-color;
         height:28px;
-        color:#fff;
+        color: @fff;
         font-size:14px;
         line-height:28px;
         padding-left: 10px;
+        cursor: move;
         .title{
           margin-left:4px;
           font-weight: @font-medium;
@@ -2647,8 +2761,6 @@ export default class ModelEdit extends Vue {
             height: 100%;
             .el-checkbox {
               width: 100%;
-              // overflow: hidden;
-              // text-overflow: ellipsis;
               display: inline-block;
               .el-checkbox__label {
                 width: calc(~'100% - 20px');
@@ -2933,44 +3045,7 @@ export default class ModelEdit extends Vue {
         background: @grey-2;
       }
     }
-    .search-result-box {
-      box-shadow: 0 0px 2px 0 @color-text-placeholder;
-      background-color: rgba(255, 255, 255, 1);
-      height:calc(~'100vh - 464px')!important;
-      min-height:250px;
-      .search-position();
-      .search-noresult {
-        font-size:20px;
-        text-align: center;
-        margin-top:100px;
-        color:@text-placeholder-color;
-      }
-      .search-group {
-        padding-top: 5px;
-        padding-bottom: 5px;
-      }
-      .search-content {
-        &.active,&:hover{
-          background-color:@base-color-9;
-        }
-        cursor:pointer;
-        height:32px;
-        line-height:32px;
-        padding-left: 20px;
-        .search-category {
-          font-size:12px;
-          color:@text-normal-color;
-        }
-        .search-name {
-          i {
-            color:@base-color;
-            font-style: normal;
-          }
-          font-size:14px;
-          color:@text-title-color;
-        }
-      }
-    }
+    
     .search-action-result {
       width:620px;
       // margin: 0 auto;
@@ -3022,9 +3097,9 @@ export default class ModelEdit extends Vue {
   }
   .tool-icon-group {
     position:absolute;
-    width:32px;
-    top:12px;
-    right:10px;
+    width: 32px;
+    top: 42px;
+    right: 10px;
     .tool-icon {
       box-shadow: @box-shadow;
       background:@text-normal-color;
@@ -3038,38 +3113,17 @@ export default class ModelEdit extends Vue {
       }
     }
   }
-  .sub-tool-icon-group {
-    position:absolute;
-    right:10px;
-    top:258px;
-    width:32px;
-    .tool-icon{
-      &.broken-location i{
-        color:@error-color-1;
-      }
-      position:relative;
-      height:30px;
-      line-height:30px;
-      i {
-        color:@text-normal-color;
-        font-size:18px;
-        &:hover{
-          color:@base-color;
-        }
-      }
-    }
-  }
   .icon-ds {
-    top:10px;
-    left:10px;
-    background:@text-normal-color;
-    color:#fff;
+    top: 40px;
+    left: 10px;
+    background: @text-normal-color;
+    color: @fff;
     box-shadow: @box-shadow;
     &.active{
-      background:@base-color;
+      background: @base-color;
     }
     &:hover{
-      background:@base-color;
+      background: @base-color;
     }
   }
   .icon-lock-status {
@@ -3081,26 +3135,38 @@ export default class ModelEdit extends Vue {
     }
     border:solid 1px @text-normal-color;
     &:hover{
-      color: #fff;
-      background-color:@normal-color-1;
+      color: @fff;
+      background-color: @normal-color-1;
       border:solid 1px @normal-color-1;
     }
   }
   .unlock-icon {
     &:hover{
       color: @fff;
-      background-color:@base-color;
+      background-color: @base-color;
       border:solid 1px @base-color;
     }
   }
   .model-edit {
     height: 100%;
-    position:relative;
+    position: relative;
     .drag-svg {
       z-index: 50;
     }
     svg.jtk-connector {
       cursor: pointer;
+      &.jtk-hover:not(.is-broken):not(.is-focus) {
+        > path {
+          stroke: @base-color-6;
+          stroke-width: 2;
+        }
+      }
+      &.jtk-hover.is-broken {
+        > path {
+          stroke: @ke-color-danger;
+          stroke-width: 2;
+        }
+      }
     }
   }
   .edit-table-layout {
@@ -3116,12 +3182,12 @@ export default class ModelEdit extends Vue {
     position: absolute;
     // box-shadow: @fact-shadow;
     border-radius: 6px;
-    border: 2px solid #E6EBF4;
+    border: 2px solid @ke-border-secondary;
     box-sizing: border-box;
     display: flex;
     flex-direction: column;
     &.is-focus {
-      border: 2px solid #0867BF;
+      border: 2px solid @line-border-focus;
       .column-list-box {
         .column-li.drag-column-li-in {
           border: 0 !important;
@@ -3134,17 +3200,17 @@ export default class ModelEdit extends Vue {
       border: 2px solid @ke-color-danger;
     }
     &.link-hover:not(.is-focus) {
-      border: 2px solid #9DCEFB;
+      border: 2px solid @base-color-6;
     }
     &:hover:not(.is-focus) {
       // box-shadow: @fact-hover-shadow;
-      border: 2px solid #9DCEFB;
+      border: 2px solid @base-color-6;
       .scrollbar-track-y{
         opacity: 1;
       }
     }
     &.is-hover:not(.is-focus) {
-      border: 2px solid #9DCEFB;
+      border: 2px solid @base-color-6;
     }
     &:focus {
       border: 2px solid @line-border-focus;
@@ -3167,13 +3233,15 @@ export default class ModelEdit extends Vue {
     &:not(.is-focus) .column-list-box {
       &.ksd-drag-box *[draggable="true"].is-link:hover {
         border: 0 !important;
-        border: 2px solid #0875DA !important;
+        border-top: 2px solid @ke-color-primary !important;
+        border-bottom: 2px solid @ke-color-primary !important;
       }
       .column-li {
         &.is-link {
           &.is-hover {
-            background-color: #CEE6FD;
-            border: 2px solid @line-border-drag;
+            background-color: @base-color-8;
+            border-top: 2px solid @line-border-drag;
+            border-bottom: 2px solid @line-border-drag;
           }
         }
       }
@@ -3181,19 +3249,28 @@ export default class ModelEdit extends Vue {
     &.is-focus .column-list-box {
       &.ksd-drag-box *[draggable="true"].is-link:hover {
         border: 0 !important;
-        border-top: 2px solid #0875DA !important;
-        border-bottom: 2px solid #0875DA !important;
+        border-top: 2px solid @ke-color-primary !important;
+        border-bottom: 2px solid @ke-color-primary !important;
       }
       .column-li {
         &.is-link {
           &.is-hover {
-            background-color: #CEE6FD;
+            background-color: @base-color-8;
             border-top: 2px solid @line-border-drag;
             border-bottom: 2px solid @line-border-drag;
           }
         }
       }
     }
+    .has-no-connected-column {
+      text-align: center;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -30%);
+      color: @text-placeholder-color;
+      white-space: pre;
+    }
     .no-data {
       position: initial;
       margin-top: 32px;
@@ -3201,20 +3278,20 @@ export default class ModelEdit extends Vue {
     // overflow: hidden;
     .table-title {
       background-color: @fact-title-color;
-      color:#fff;
+      color:@fff;
       line-height: 38px;
       border-radius: 4px 4px 0 0;
       height: 38px;
       padding: 0 8px 0 12px;
       display: flex;
       align-items: center;
+      cursor: move;
       &.table-spread-out {
         border-radius: 4px;
       }
       .table-sign {
         display: inline-block;
         font-size: 0;
-        // cursor: pointer;
       }
       .table-column-nums {
         font-size: 12px;
@@ -3239,15 +3316,19 @@ export default class ModelEdit extends Vue {
       .table-dropdown {
         font-size: 0;
       }
+      .custom-tooltip-layout {
+        height: 100%;
+        cursor: default;
+      }
       .name {
-        text-overflow: ellipsis;
-        overflow: hidden;
         line-height: 29px\0;
         width: calc(~"100% - 50px");
+        height: 100%;
         display: inline-block;
         margin-left: 4px;
         font-weight: bold;
-        &.tip_box {
+        .tip_box {
+          cursor: move;
           .alias-span {
             font-size: 14px;
             font-weight: @font-medium;
@@ -3255,9 +3336,6 @@ export default class ModelEdit extends Vue {
         }
       }
       span {
-        // width:24px;
-        // height:24px;
-        // float:left;
         line-height: 30px\0;
       }
       .kind {
@@ -3265,7 +3343,6 @@ export default class ModelEdit extends Vue {
         margin: 0;
       }
       .kind:hover {
-        // background-color:@base-color;
         color:@grey-3;
       }
       i {
@@ -3287,44 +3364,40 @@ export default class ModelEdit extends Vue {
     }
     .column-list-box {
       overflow: auto;
-      // border-top: solid 1px @line-border-color;
       overflow-x: hidden;
       flex: 1;
+      border-radius: 0 0 5px 5px;
       &.ksd-drag-box *[draggable="true"]:not(.is-link):hover {
         border: 0 !important;
-        border-top: 2px solid #9DCEFB !important;
-        border-bottom: 2px solid #9DCEFB !important;
+        border-top: 2px solid @base-color-6 !important;
+        border-bottom: 2px solid @base-color-6 !important;
       }
-      // &.ksd-drag-box *[draggable="true"].is-link:hover {
-      //   border: 0 !important;
-      //   border: 2px solid #0875DA !important;
-      // }
       .column-li {
         cursor: default;
       }
       ul {
         li {       
-          padding-left:5px;
+          padding-left: 5px;
           cursor: move;
           border-top: solid 2px transparent;
           border-bottom: 2px solid transparent;
           height: 32px;
           line-height: 32px;
-          font-size:14px;
+          font-size: 14px;
           position: relative;
           &.is-hover {
             background-color: @ke-background-color-secondary;
-            border-top: 2px solid #9DCEFB;
-            border-bottom: 2px solid #9DCEFB;
+            border-top: 2px solid @base-color-6;
+            border-bottom: 2px solid @base-color-6;
           }
           &.is-focus {
-            background-color: #CEE6FD;
+            background-color: @base-color-8;
             border-top: 2px solid @line-border-drag;
             border-bottom: 2px solid @line-border-drag;
           }
           &:hover{
-            border-top: 2px solid #9DCEFB;
-            border-bottom: 2px solid #9DCEFB;
+            border-top: 2px solid @base-color-6;
+            border-bottom: 2px solid @base-color-6;
             background-color: @ke-color-info-bg;
           }
           &.is-link {
@@ -3333,23 +3406,26 @@ export default class ModelEdit extends Vue {
               color: @ke-color-primary;
               font-weight: @font-medium;
             }
-            // &.is-hover {
-            //   background-color: #CEE6FD;
-            //   border: 2px solid @line-border-drag;
-            // }
             &.is-focus {
-              background: #CEE6FD;
+              background: @base-color-8;
             }
             &:hover {
-              background: #CEE6FD;
+              background: @base-color-8;
             }
           }
           .ksd-nobr-text {
             width: calc(~'100% - 25px');
+            display: flex;
+            align-items: center;
+            .tip_box {
+              height: 32px;
+              line-height: 30px;
+            }
           }
           .col-type-icon {
-            color:@text-disabled-color;
-            font-size:12px;
+            color: @text-disabled-color;
+            font-size: 12px;
+            margin-right: 5px;
             .is-pfk{
               color: @text-placeholder-color;
               position: absolute;
@@ -3385,6 +3461,7 @@ export default class ModelEdit extends Vue {
           padding-left: 0;
           &:hover{
             background-color: transparent;
+            border: 2px solid transparent;
           }
         }
       }
@@ -3393,14 +3470,14 @@ export default class ModelEdit extends Vue {
         margin-right: 2px;
         .scrollbar-thumb-y {
           width: 4px;
-          background: #ECF0F8;
+          background: @ke-background-color-hover;
         }
       }
     }
   }
   .column-point-dot {
     z-index: 100;
-    border: 2px solid #9DCEFB;
+    border: 2px solid @base-color-6;
     border-radius: 100%;
     height: 8px !important;
     width: 8px !important;
@@ -3413,7 +3490,7 @@ export default class ModelEdit extends Vue {
     }
     .add-point {
       position: absolute;
-      color: #9DCEFB;
+      color: @base-color-6;
       z-index: 20;
       cursor: pointer;
       width: 100%;
@@ -3424,9 +3501,9 @@ export default class ModelEdit extends Vue {
       font-size: 16px;
     }
     &.is-focus {
-      border: 3px solid #0875DA;
+      border: 3px solid @ke-color-primary;
       .add-point {
-        color: #0875DA;
+        color: @ke-color-primary;
       }
     }
   }
@@ -3435,9 +3512,9 @@ export default class ModelEdit extends Vue {
     z-index: 100;
     .line-end-pointer {
       font-size: 20px;
-      color: #9DCEFB;
+      color: @base-color-6;
       &:hover {
-        color: #0875DA;
+        color: @ke-color-primary;
       }
     }
   }
@@ -3445,10 +3522,9 @@ export default class ModelEdit extends Vue {
 .error-font {
   color: @error-color-1;
 }
-.table-actions {
+.table-actions-dropdown {
   transform: translate(106%, 0);
   margin-top: -38px !important;
-  // margin-left: 26px;
   width: 200px;
   .spread-or-expand-table {
     display: flex;
@@ -3461,13 +3537,35 @@ export default class ModelEdit extends Vue {
   }
 }
 .model-alias-tooltip {
-  margin-top: -10px !important;
+  margin-top: -5px !important;
 }
 #custom-drag-image {
-  color: #0875DA;
+  color: @ke-color-primary;
   font-size: 18px;
   position: absolute;
   background: transparent;
   opacity: 0;
 }
+#broken-use-group:hover{
+  > path:not(#use) {
+    stroke: @ke-color-danger;
+    stroke-width: 2;
+  }
+}
+#use-group:hover {
+  > path:not(#use) {
+    stroke: @base-color-6;
+    stroke-width: 2;
+  }
+}
+.model-action-tools {
+  .el-dropdown-menu__item {
+    i {
+      margin-top: -2px;
+    }
+    &.is-active {
+      color: @ke-color-primary;
+    }
+  }
+}
 </style>
\ No newline at end of file
diff --git a/kystudio/src/components/studio/StudioModel/ModelEdit/layout.js b/kystudio/src/components/studio/StudioModel/ModelEdit/layout.js
index 47d56336cd..51febb1658 100644
--- a/kystudio/src/components/studio/StudioModel/ModelEdit/layout.js
+++ b/kystudio/src/components/studio/StudioModel/ModelEdit/layout.js
@@ -5,6 +5,7 @@ class Tree {
     this.boxH = modelRenderConfig.tableBoxHeight
     this.boxML = modelRenderConfig.tableBoxLeft
     this.boxMT = modelRenderConfig.tableBoxTop
+    this.tables = options.tables
     this.josnId = 0
     this.rootGuid = options.rootGuid
     this.showLinkCons = options.showLinkCons
@@ -14,7 +15,22 @@ class Tree {
     if (!this.showLinkCons) {
       throw new Error('showLinkCons is required for Tree Layout')
     }
-    this.nodeDB = new NodeDB(this.getNodeStructure(), this)
+    const rootNode = this.getNodeStructure()
+    rootNode.rightNodes = { guid: this.rootGuid, _json_id: 0, children: rootNode.children.filter((n, i) => i % 2 === 0) }
+    rootNode.leftNodes = { guid: this.rootGuid, _json_id: 0, children: rootNode.children.filter((n, i) => i % 2 !== 0) }
+    this.nodeDB = new NodeDB(rootNode, this)
+    this.nodeDB.getRootNode().rightNodes = new NodeDB(rootNode.rightNodes, this, 'rightNodes')
+    if (rootNode.leftNodes.children.length > 0) {
+      this.nodeDB.getRootNode().leftNodes = new NodeDB(rootNode.leftNodes, this, 'leftNodes')
+    }
+  }
+
+  getNodeBoundingClientRect (node) {
+    const defaultBox = { height: this.boxH, width: this.boxW }
+    if (!node) return { nodeHeight: defaultBox.height, nodeWidth: defaultBox.width }
+    const nodeGuid = node.guid
+    const { height: nodeHeight, width: nodeWidth } = this.tables[nodeGuid]?.drawSize ?? defaultBox
+    return { nodeHeight, nodeWidth }
   }
 
   getNodeStructure (root) {
@@ -37,37 +53,43 @@ class Tree {
   positionTree () {
     const rootNode = this.root()
     this.resetLevelData()
-    this.firstWalk(rootNode, 0)
-    this.secondWalk(rootNode, 0, 0, 0)
+    this.firstWalk(rootNode.rightNodes.db[0], 0, 'rightNodes') // rootNode.rightNodes.rightNodeDB[0] 默认右子树的第一个node就是rootNode
+    this.secondWalk(rootNode.rightNodes.db[0], 0, 0, 0, 'rightNodes')
+    if (rootNode.leftNodes) {
+      this.firstWalk(rootNode.leftNodes.db[0], 0, 'leftNodes')
+      this.secondWalk(rootNode.leftNodes.db[0], 0, 0, 0, 'leftNodes', true)
+      this.thirdWalk(rootNode.leftNodes.db[0], rootNode.rightNodes.db[0], 'leftNodes', true)
+    }
 
     return this
   }
 
-  firstWalk (node, level) {
+  firstWalk (node, level, nodeType) {
     node.prelim = null
     node.modifier = null
-    this.setNeighbors(node, level)
+    this.setNeighbors(node, level, nodeType)
     const leftSibling = node.leftSibling()
+    const { nodeHeight } = this.getNodeBoundingClientRect(leftSibling)
 
     if (node.children.length === 0) {
       // set preliminary x-coordinate
       if (leftSibling) {
-        node.prelim = leftSibling.prelim + this.boxW + this.boxML
+        node.prelim = leftSibling.prelim + nodeHeight + this.boxMT
       } else {
         node.prelim = 0
       }
     } else {
       // node is not a leaf,  firstWalk for each child
       for (let i = 0; i < node.children.length; i++) {
-        this.firstWalk(node.childAt(i), level + 1)
+        this.firstWalk(node.childAt(nodeType, i), level + 1, nodeType)
       }
 
-      const midPoint = node.childrenCenter(this.boxW) - this.boxW / 2
+      const midPoint = node.childrenCenter(nodeType, nodeHeight) - nodeHeight / 2
 
       if (leftSibling) {
-        node.prelim = leftSibling.prelim + this.boxW + this.boxML
+        node.prelim = leftSibling.prelim + nodeHeight + this.boxMT
         node.modifier = node.prelim - midPoint
-        this.apportion(node, level)
+        this.apportion(node, level, nodeType)
       } else {
         node.prelim = midPoint
       }
@@ -75,8 +97,8 @@ class Tree {
     return this
   }
 
-  apportion (node, level) {
-    let firstChild = node.firstChild()
+  apportion (node, level, nodeType) {
+    let firstChild = node.firstChild(nodeType)
     let firstChildLeftNeighbor = firstChild.leftNeighbor()
     let compareDepth = 1
 
@@ -86,6 +108,7 @@ class Tree {
       let modifierSumLeft = 0
       let leftAncestor = firstChildLeftNeighbor
       let rightAncestor = firstChild
+      const { nodeHeight } = this.getNodeBoundingClientRect(node)
 
       for (let i = 0; i < compareDepth; i++) {
         leftAncestor = leftAncestor.parent()
@@ -96,7 +119,7 @@ class Tree {
 
       // find the gap between two trees and apply it to subTrees
       // and mathing smaller gaps to smaller subtrees
-      let totalGap = firstChildLeftNeighbor.prelim + modifierSumLeft + this.boxW + this.boxML - (firstChild.prelim + modifierSumRight)
+      let totalGap = firstChildLeftNeighbor.prelim + modifierSumLeft + nodeHeight + this.boxMT - (firstChild.prelim + modifierSumRight)
 
       if (totalGap > 0) {
         let subtreeAux = node
@@ -123,7 +146,7 @@ class Tree {
       }
       compareDepth++
 
-      firstChild = (firstChild.children.length === 0) ? node.leftMost(0, compareDepth) : firstChild.firstChild()
+      firstChild = (firstChild.children.length === 0) ? node.leftMost(0, compareDepth, nodeType) : firstChild.firstChild(nodeType)
 
       if (firstChild) {
         firstChildLeftNeighbor = firstChild.leftNeighbor()
@@ -131,47 +154,68 @@ class Tree {
     }
   }
 
-  secondWalk (node, level, X, Y) {
-    const xTmp = node.prelim + X
-    const yTmp = Y
+  secondWalk (node, level, X, Y, nodeType, isLeft) {
+    const { nodeWidth } = this.getNodeBoundingClientRect(node)
+    const xTmp = X
+    const yTmp = node.prelim + Y
     node.X = xTmp
     node.Y = yTmp
 
     if (node.children.length !== 0) {
-      this.secondWalk(node.firstChild(), level + 1, X + node.modifier, Y + this.boxH + this.boxMT)
+      const XX = isLeft ? X - nodeWidth - this.boxML : X + nodeWidth + this.boxML
+      this.secondWalk(node.firstChild(nodeType), level + 1, XX, Y + node.modifier, nodeType, isLeft)
     }
 
     if (node.rightSibling()) {
-      this.secondWalk(node.rightSibling(), level, X, Y)
+      this.secondWalk(node.rightSibling(), level, X, Y, nodeType, isLeft)
+    }
+  }
+
+  thirdWalk (node, targetNode, nodeType, isRoot, modifier) {
+    const modifierMove = isRoot ? targetNode.prelim - node.prelim : modifier
+    if (modifierMove && isRoot || !isRoot) {
+      node.prelim = node.prelim + modifierMove
+      node.Y = node.Y + modifierMove
+      if (node.children.length !== 0) {
+        this.thirdWalk(node.firstChild(nodeType), targetNode, nodeType, false, modifierMove)
+      }
+      if (node.rightSibling()) {
+        this.thirdWalk(node.rightSibling(), targetNode, nodeType, false, modifierMove)
+      }
     }
   }
 
-  setNeighbors (node, level) {
-    node.leftNeighborId = this.lastNodeOnLevel[level]
+  setNeighbors (node, level, lastNodeOnLevelType) {
+    node.leftNeighborId = this[lastNodeOnLevelType + 'OnLevel'][level]
     if (node.leftNeighborId) {
       node.leftNeighbor().rightNeighborId = node.id
     }
-    this.lastNodeOnLevel[level] = node.id
+    this[lastNodeOnLevelType + 'OnLevel'][level] = node.id
     return this
   }
 
   resetLevelData () {
-    this.lastNodeOnLevel = []
+    this.rightNodesOnLevel = []
+    this.leftNodesOnLevel = []
     return this
   }
 
   root () {
-    return this.nodeDB.get(0)
+    return this.nodeDB.getRootNode()
   }
 }
 
 class NodeDB {
-  constructor (nodeStructure, tree) {
-    this.db = []
+  constructor (nodeStructure, tree, nodeType) {
+    if (nodeType) {
+      this.db = []
+    } else {
+      this.rootNode = null
+    }
     const self = this
     const iterateChildren = function (node, parentId) {
-      const newNode = self.createNode(node, parentId, tree)
-      if (node.children) {
+      const newNode = self.createNode(node, parentId, tree, nodeType)
+      if (node.children && nodeType) {
         for (let i = 0; i < node.children.length; i++) {
           iterateChildren(node.children[i], newNode.id)
         }
@@ -185,9 +229,21 @@ class NodeDB {
     return this.db[nodeId]
   }
 
-  createNode (nodeStructure, parentId, tree) {
-    const node = new TreeNode(nodeStructure, this.db.length, parentId, tree)
-    this.db.push(node)
+  getChildNode (nodeType, nodeId) {
+    return this.rootNode[nodeType].db[nodeId]
+  }
+
+  getRootNode () {
+    return this.rootNode
+  }
+
+  createNode (nodeStructure, parentId, tree, nodeType) {
+    const node = new TreeNode(nodeStructure, nodeType ? this.db.length : 0, parentId, tree, nodeType)
+    if (!nodeType) {
+      this.rootNode = node
+    } else {
+      this.db.push(node)
+    }
     // skip root node (0)
     if (parentId >= 0) {
       const parent = this.get(parentId)
@@ -198,11 +254,12 @@ class NodeDB {
 }
 
 class TreeNode {
-  constructor (nodeStructure, id, parentId, tree) {
+  constructor (nodeStructure, id, parentId, tree, treeType) {
     this.id = id
     this.guid = nodeStructure.guid
     this.parentId = parentId
     this.tree = tree
+    this.treeType = treeType
     this.prelim = 0
     this.modifier = 0
     this.leftNeighborId = null
@@ -211,20 +268,20 @@ class TreeNode {
     return this
   }
 
-  dbGet (nodeId) {
-    return this.getTreeNodeDb().get(nodeId)
+  dbGet (nodeType, nodeId) {
+    return this.getTreeNodeDb().getChildNode(nodeType, nodeId)
   }
 
-  childAt (index) {
-    return this.dbGet(this.children[index])
+  childAt (nodeType, index) {
+    return this.dbGet(nodeType, this.children[index])
   }
 
-  firstChild () {
-    return this.childAt(0)
+  firstChild (nodeType) {
+    return this.childAt(nodeType, 0)
   }
 
-  lastChild () {
-    return this.childAt(this.children.length - 1)
+  lastChild (nodeType) {
+    return this.childAt(nodeType, this.children.length - 1)
   }
 
   getTreeNodeDb () {
@@ -232,7 +289,7 @@ class TreeNode {
   }
 
   lookupNode (nodeId) {
-    return this.getTreeNodeDb().get(nodeId)
+    return this.getTreeNodeDb().getChildNode(this.treeType, nodeId)
   }
 
   parent () {
@@ -266,13 +323,13 @@ class TreeNode {
     }
   }
 
-  childrenCenter (boxW) {
-    const first = this.firstChild()
-    const last = this.lastChild()
-    return (first.prelim + ((last.prelim - first.prelim) + boxW) / 2)
+  childrenCenter (nodeType, boxH) {
+    const first = this.firstChild(nodeType)
+    const last = this.lastChild(nodeType)
+    return (first.prelim + ((last.prelim - first.prelim) + boxH) / 2)
   }
 
-  leftMost (level, depth) {
+  leftMost (level, depth, nodeType) {
     if (level >= depth) {
       return this
     }
@@ -281,7 +338,7 @@ class TreeNode {
     }
 
     for (let i = 0, n = this.children.length; i < n; i++) {
-      const leftmostDescendant = this.childAt(i).leftMost(level + 1, depth)
+      const leftmostDescendant = this.childAt(nodeType, i).leftMost(level + 1, depth, nodeType)
       if (leftmostDescendant) {
         return leftmostDescendant
       }
diff --git a/kystudio/src/components/studio/StudioModel/ModelEdit/locales.js b/kystudio/src/components/studio/StudioModel/ModelEdit/locales.js
index cd0c54a620..4e169e47b1 100644
--- a/kystudio/src/components/studio/StudioModel/ModelEdit/locales.js
+++ b/kystudio/src/components/studio/StudioModel/ModelEdit/locales.js
@@ -100,7 +100,13 @@ export default {
     expandTableColumns: 'Expand',
     noResults: 'No result',
     searchResults: '{number} results',
-    doubleClick: 'Double Click',
-    loseFactTableAlert: 'The current model is missing a fact table, please set up a fact table.'
+    doubleClick: 'Double Click Header',
+    loseFactTableAlert: 'The current model is missing a fact table, please set up a fact table.',
+    discardChange: 'Discard Changes',
+    continueEditing: 'Continue Editing',
+    betaSearchTips: 'The current search is beta version. Having search problems? ',
+    feedback: 'Go to feedback',
+    introductionUrl: 'http://kyligence.io/enterprise/#analytics',
+    noConnectedColumn: 'No Connected Column'
   }
 }
diff --git a/kystudio/src/components/studio/StudioModel/ModelEdit/model.js b/kystudio/src/components/studio/StudioModel/ModelEdit/model.js
index 8d29ff1920..8357231de8 100644
--- a/kystudio/src/components/studio/StudioModel/ModelEdit/model.js
+++ b/kystudio/src/components/studio/StudioModel/ModelEdit/model.js
@@ -72,25 +72,34 @@ class NModel extends Schama {
   renderPosition () {
     // 自动布局前先理顺链表方向
     // this._arrangeLinks()
-    const layers = this.autoCalcLayer()
+    const layersRoot = this.autoCalcLayer()
+    let layers = layersRoot?.rightNodes?.db
+    if (layersRoot?.leftNodes) {
+      layers = [...layers, ...layersRoot.leftNodes.db]
+    }
     if (layers && layers.length > 0) {
       const baseL = modelRenderConfig.baseLeft
       const baseT = modelRenderConfig.baseTop
       const centerL = $(this.renderDom).width() / 2 - modelRenderConfig.tableBoxWidth / 2
-      const moveL = layers[0].X - centerL
+      const centerLeft = $(this.renderDom).width() / 4 - modelRenderConfig.tableBoxWidth / 2
+
+      const moveL = layersRoot.leftNodes ? layers[0].X - centerL : layers[0].X - centerLeft
       this.renderDom.style.cssText += `margin-left: 0; margin-top: 0;`
       this._mount.marginClient.top = 0
       this._mount.marginClient.left = 0
       for (let k = 0; k < layers.length; k++) {
+        const centerT = $(this.renderDom).height() / 3 - this.tables[layers[k].guid].drawSize.height / 2
+        const moveT = layers[0].Y - centerT
         var currentTable = this.getTableByGuid(layers[k].guid)
         currentTable.drawSize.left = baseL - moveL + layers[k].X
-        currentTable.drawSize.top = baseT + layers[k].Y
+        currentTable.drawSize.top = baseT - moveT + layers[k].Y
         currentTable.drawSize.width = modelRenderConfig.tableBoxWidth
-        currentTable.drawSize.height = modelRenderConfig.tableBoxHeight
+         // !this.vm.showOnlyConnectedColumn && (currentTable.drawSize.height = modelRenderConfig.tableBoxHeight)
         currentTable.checkIsOutOfView(this._mount, currentTable.drawSize, this._mount.windowWidth, this._mount.windowHeight, layers[k].guid)
       }
       this.vm.$nextTick(() => {
         this.plumbTool.refreshPlumbInstance()
+        this.createAndUpdateSvgGroup(null, {type: 'update'})
       })
     }
   }
@@ -222,7 +231,8 @@ class NModel extends Schama {
           }
         }, {
           joinType: joinInfo?.join?.type ?? '',
-          brokenLine: isBrokenLine
+          brokenLine: isBrokenLine,
+          cancelBubble: true
         })
         this.setOverLayLabel(conn, isBrokenLine)
         this.plumbTool.refreshPlumbInstance()
@@ -529,7 +539,7 @@ class NModel extends Schama {
     }
     for (let t in this.tables) {
       let ntable = this.tables[t]
-      canvasInfo.coordinate[ntable.alias] = ntable.getMetaCanvasInfo()
+      canvasInfo.coordinate[ntable.alias] = { ...ntable.getMetaCanvasInfo(), isSpread: true }
     }
     canvasInfo.zoom = this._mount.zoom
     canvasInfo.marginClient = this._mount.marginClient
@@ -649,7 +659,7 @@ class NModel extends Schama {
   }
   search (keywords) {
     var stables = this.searchTable(keywords)
-    var smeasures = this.searchMeasure(keywords)
+    var smeasures = this.searchMeasure(keywords).filter(it => it.name !== 'COUNT_ALL') // COUNT_ALL 度量不允许编辑
     var sdimensions = this.searchDimension(keywords)
     var sjoins = this.searchJoin(keywords)
     var scolumns = this.searchColumn(keywords)
@@ -1002,6 +1012,13 @@ class NModel extends Schama {
     })
     pathObj(t).zIndex = maxZindex
   }
+  checkOutsideByTables () {
+    for (var i in this.tables) {
+      var curTable = this.tables[i]
+      curTable.checkIsOutOfView(this._mount, curTable.drawSize, this._mount.windowWidth, this._mount.windowHeight, i)
+      this.vm.hideLinkLabel(this.getAllConnectsByGuid(curTable.guid), curTable)
+    }
+  }
   setZoom (zoom) {
     this.plumbTool.setZoom(zoom / 10)
     this.getZoomSpace()
@@ -1011,12 +1028,14 @@ class NModel extends Schama {
     var nextZoom = this._mount.zoom + 1 > 10 ? 10 : this._mount.zoom += 1
     this.plumbTool.setZoom(nextZoom / 10)
     this.getZoomSpace()
+    this.checkOutsideByTables()
   }
   // 缩小视图
   reduceZoom () {
     var nextZoom = this._mount.zoom - 1 < 4 ? 4 : this._mount.zoom -= 1
     this.plumbTool.setZoom(nextZoom / 10)
     this.getZoomSpace()
+    this.checkOutsideByTables()
   }
   getZoomSpace () {
     if (this.renderDom) {
@@ -1037,13 +1056,7 @@ class NModel extends Schama {
     }
     this._mount.marginClient.left += x
     this._mount.marginClient.top += y
-    for (var i in this.tables) {
-      var curTable = this.tables[i]
-      // curTable.drawSize.left += x
-      // curTable.drawSize.top += y
-      curTable.checkIsOutOfView(this._mount, curTable.drawSize, this._mount.windowWidth, this._mount.windowHeight, i)
-      this.vm.hideLinkLabel(this.getAllConnectsByGuid(curTable.guid), curTable)
-    }
+    this.checkOutsideByTables()
     this.vm.$nextTick(() => {
       this.plumbTool.refreshPlumbInstance()
     })
@@ -1074,7 +1087,8 @@ class NModel extends Schama {
       options.fact = tableInfo.fact
       options.batch_table_identity = tableInfo.batch_table_identity
       options.modelEvents = {
-        getAllConnectsByGuid: this.getAllConnectsByGuid.bind(this)
+        getAllConnectsByGuid: this.getAllConnectsByGuid.bind(this),
+        createAndUpdateSvgGroup: this.createAndUpdateSvgGroup.bind(this)
       }
       if (tableInfo.source_type === 1 && !options.isSecStorageEnabled) {
         if (!this.getFactTable()) {
@@ -1395,9 +1409,9 @@ class NModel extends Schama {
       return
     }
     const rootGuid = factTable.guid
-    const tree = new ModelTree({rootGuid: rootGuid, showLinkCons: this.allConnInfo})
+    const tree = new ModelTree({rootGuid: rootGuid, showLinkCons: this.allConnInfo, tables: this.tables})
     tree.positionTree()
-    return tree.nodeDB.db
+    return tree.nodeDB.rootNode
   }
   // 添加连接点
   addPlumbPoints (guid, columnName, columnType, isBroken) {
@@ -1443,31 +1457,32 @@ class NModel extends Schama {
     var pid = conn.targetId
     var labelObj = conn.getOverlay(pid + (fid + 'label'))
     var joinInfo = this.tables[pid].getJoinInfoByFGuid(fid)
-    if (!joinInfo) {
-      return
-    }
+    if (!joinInfo) return
     var joinType = joinInfo.join.type
     var labelCanvas = $(labelObj.canvas)
+    const [lineCanvas] = $(conn.canvas)
     const fKeys = joinInfo.join.foreign_key.map(it => it.split('.')[1])
     const pKeys = joinInfo.join.primary_key.map(it => it.split('.')[1])
-    // let tooltipId = labelCanvas?.find('.el-tooltip')?.eq(0)?.attr('aria-describedby')
+    lineCanvas.setAttribute('class', `${lineCanvas.className.baseVal.replace(/is-broken/g, '')}`)
     labelCanvas.removeClass('link-label-broken')
     conn.setType(isBroken ? 'broken' : 'normal')
     conn.isBroken = isBroken
     this.getBrokenLinkedTable()
     labelCanvas.addClass(isBroken ? 'link-label link-label-broken' : `link-label ${fid}&${pid}`)
+    isBroken && lineCanvas.setAttribute('class', `${lineCanvas.className.baseVal} is-broken`)
+    this.createAndUpdateSvgGroup(lineCanvas, {type: 'create', isBroken, fKeys, pKeys, fid, pid})
 
     const child = document.createElement('i')
     child.className = 'close-icon el-ksd-n-icon-close-outlined'
     const hideNode = document.createElement('span')
     hideNode.className = 'join-type-hide'
-    // labelCanvas && labelCanvas.find('.label').eq(0).text(joinType)
-    // tooltipId && $(`#${tooltipId}`)
     let dom = !isBroken ? createToolTipDom(`<span class="join-type ${joinType === 'INNER' ? 'el-ksd-n-icon-inner-join-filled' : joinType === 'LEFT' ? 'el-ksd-n-icon-left-join-filled' : 'el-ksd-n-icon-right-join-filled'}"></span>`, {
       text: joinType,
+      className: 'line-label-bar',
       children: [hideNode, child]
     }) : createToolTipDom(`<span class="join-type el-ksd-icon-wrong_fill_16"></span>`, {
       text: joinType,
+      className: 'line-label-bar',
       children: [hideNode, child]
     })
     dom.onmouseenter = function () {
@@ -1521,6 +1536,53 @@ class NModel extends Schama {
     const currentJoin = joinColumns.foreign_key.map((f, index) => `${f}/${joinColumns.op[index]}/${joinColumns.primary_key[index]}/${joinColumns.type}`).sort().join('&')
     return linkList.includes(currentJoin)
   }
+
+  // 自定义扩大 line svg hover 热区
+  createAndUpdateSvgGroup (lineCanvas, conn, guid) {
+    if (conn.type === 'create') {
+      const sign = 'http://www.w3.org/2000/svg'
+      const path = lineCanvas.firstChild
+      if (!path) return
+      if (lineCanvas.querySelector('g')) return
+      const newPath = path.cloneNode(true)
+      const group = document.createElementNS(sign, 'g')
+      const d = path.getAttribute('d')
+
+      group.id = conn.isBroken ? 'broken-use-group' : 'use-group'
+      newPath.setAttribute('d', d)
+      newPath.setAttribute('stroke-width', 20)
+      newPath.setAttribute('stroke', 'transparent')
+      newPath.setAttribute('id', 'use')
+      group.appendChild(path)
+      group.appendChild(newPath)
+      lineCanvas.appendChild(group)
+
+      group.onmouseenter = function () {
+        $(`#${conn.fid}`).addClass('link-hover')
+        $(`#${conn.pid}`).addClass('link-hover')
+        conn.fKeys.forEach(item => $(`#${conn.fid}_${item}`).addClass('is-hover'))
+        conn.pKeys.forEach(item => $(`#${conn.pid}_${item}`).addClass('is-hover'))
+      }
+      group.onmouseleave = function () {
+        $(`#${conn.fid}`).removeClass('link-hover')
+        $(`#${conn.pid}`).removeClass('link-hover')
+        conn.fKeys.forEach(item => $(`#${conn.fid}_${item}`).removeClass('is-hover'))
+        conn.pKeys.forEach(item => $(`#${conn.pid}_${item}`).removeClass('is-hover'))
+      }
+    } else {
+      const lineGroups = !guid ? Object.keys(this.allConnInfo): Object.keys(this.allConnInfo).filter(it => it.split('$').includes(guid))
+      lineGroups.forEach(item => {
+        const line = this.allConnInfo[item].canvas
+        if (line.querySelector('g')) {
+          const paths = line.querySelectorAll('path')
+          const firstPathLine = paths[0].getAttribute('d')
+          paths[1].setAttribute('d', firstPathLine)
+        } else {
+          this.createAndUpdateSvgGroup(line, this.allConnInfo[item].isBroken, 'create')
+        }
+      })
+    }
+  }
 }
 
 export default NModel
diff --git a/kystudio/src/components/studio/StudioModel/ModelEdit/table.js b/kystudio/src/components/studio/StudioModel/ModelEdit/table.js
index ae814f9869..2c0406f32a 100644
--- a/kystudio/src/components/studio/StudioModel/ModelEdit/table.js
+++ b/kystudio/src/components/studio/StudioModel/ModelEdit/table.js
@@ -70,6 +70,7 @@ class NTable {
         this.exchangeMoreTableColumns()
         options.plumbTool.lazyRender(() => {
           options.plumbTool.refreshPlumbInstance()
+          this.modelEvents.createAndUpdateSvgGroup(null, {type: 'update'}, this.guid)
         })
       }
     }, options.drawSize)
diff --git a/kystudio/src/components/studio/StudioModel/ModelList/AggregateModal/index.vue b/kystudio/src/components/studio/StudioModel/ModelList/AggregateModal/index.vue
index 33332393be..e8fc98a331 100644
--- a/kystudio/src/components/studio/StudioModel/ModelList/AggregateModal/index.vue
+++ b/kystudio/src/components/studio/StudioModel/ModelList/AggregateModal/index.vue
@@ -13,7 +13,6 @@
           show-icon>
         </el-alert>
       </div>
-      <!-- <div class="loading" v-if="isLoading" v-loading="isLoading"></div> -->
       <div class="agg-group-layout">
         <div v-if="model" class="agg-list" ref="aggListLayout">
           <!-- 聚合组按钮 -->
@@ -244,7 +243,6 @@
                         <div class="ksd-mb-10">
                           <span class="title font-medium measure-title">{{$t('includeMeasure')}}</span>
                           <div class="row ksd-mb-10 ksd-fright ky-no-br-space">
-                            <!-- <el-button plain size="mini" class="add-all-item" type="primary" @click="handleAddAllMeasure(aggregateIdx, aggregate.id)">{{$t('selectAllMeasure')}}<el-tooltip class="item tip-item" popper-class='aggregate-tip' effect="dark" :content="$t('measureTabTip')" placement="bottom"><i class="el-icon-ksd-what"></i></el-tooltip></el-button> -->
                             <common-tip placement="top" :content="$t('refuseAddIndexTip')"
                               :disabled="!(!indexUpdateEnabled && ['HYBRID', 'STREAMING'].includes(aggregate.index_range))">
                               <el-button plain size="mini" class="ksd-ml-10"
@@ -259,25 +257,6 @@
                             </common-tip>
                           </div>
                         </div>
-                        <!-- <el-select
-                          multiple
-                          filterable
-                          class="mul-filter-select"
-                          :class="{'reset-padding': aggregate.measures.length}"
-                          :ref="`aggregate.measures.${aggregateIdx}`"
-                          :value="aggregate.measures"
-                          :placeholder="$t('kylinLang.common.pleaseSelectOrSearch')"
-                          @input="value => handleInput(`aggregateArray.${aggregateIdx}.measures`, value, aggregate.id)"
-                          @remove-tag="value => handleRemoveMeasureRules(value, aggregateIdx, aggregate.id)">
-                          <i slot="prefix" v-show="!aggregate.measures.length" class="el-input__icon el-ksd-icon-search_22"></i>
-                          <el-option
-                            v-for="measure in measures"
-                            :key="measure.value"
-                            :label="measure.label"
-                            :disabled="measure.value === 'COUNT_ALL'"
-                            :value="measure.value">
-                          </el-option>
-                        </el-select> -->
                         <div class="include-measure">
                           <el-tag :class="{'is-active': currentSelectedTag.ctx === item && currentSelectedTag.aggregateIdx === aggregateIdx}" size="small" v-for="(item, index) in aggregate.measures" :key="index" @click.native="handleClickTag(item, aggregate.activeTab, aggregateIdx)">{{item}}</el-tag>
                         </div>
@@ -294,7 +273,6 @@
         </div>
         <div class="metadata-detail">
           <template v-if="currentSelectedTag.type === 'dimension'">
-            <!-- && currentSelectedTag.data.sample -->
             <template v-if="currentSelectedTag.ctx && currentSelectedTag.data.simple">
               <p class="title">{{$t('statistics')}}</p>
               <el-table class="statistics_list_table"
@@ -437,8 +415,8 @@
         </div>
         <div class="right ksd-fs-0">
           <el-button :type="!onlyRealTimeType ? 'primary' : ''" :text="!onlyRealTimeType" size="medium" @click="handleClose(false)">{{$t('kylinLang.common.cancel')}}</el-button>
-          <el-button :type="onlyRealTimeType ? 'primary' : ''" size="medium" class="ksd-ml-10" :disabled="isDisabledSaveBtn" v-if="isShow" :loading="isSubmit" @click="handleSubmit(false)">{{$t('kylinLang.common.save')}}</el-button>
-          <el-button v-if="isShow && !onlyRealTimeType" type="primary" size="medium" class="ksd-ml-10" :disabled="isDisabledSaveBtn" :loading="isSubmit" @click="handleSubmit(true)">{{$t('saveAndBuild')}}</el-button>
+          <el-button :type="onlyRealTimeType ? 'primary' : ''" size="medium" class="ksd-ml-10" :disabled="isDisabledSaveBtn || isSubmit&&isCatchUpLoading" v-if="isShow" :loading="isSubmit&&!isCatchUpLoading" @click="handleSubmit(false)">{{$t('kylinLang.common.save')}}</el-button>
+          <el-button v-if="isShow && !onlyRealTimeType" type="primary" size="medium" class="ksd-ml-10" :disabled="isDisabledSaveBtn || isSubmit&&!isCatchUpLoading" :loading="isSubmit&&isCatchUpLoading" @click="handleSubmit(true)">{{$t('saveAndBuild')}}</el-button>
         </div>
       </div>
     </div>
@@ -467,10 +445,6 @@
           show-icon>
         </el-alert>
         <div class="filter-dimension">
-          <!-- <el-tooltip effect="dark" placement="bottom">
-            <div slot="content" v-html="$t('excludeTableCheckboxTip')"></div>
-            <el-checkbox class="ksd-mr-5" v-model="displayExcludedTables" @change="changeExcludedTables" v-if="showExcludedTableCheckBox">{{$t('excludeTableCheckbox')}}</el-checkbox>
-          </el-tooltip> -->
           <el-input v-model="searchName" v-global-key-event.enter.debounce="filterChange" @clear="clearFilter" size="medium" prefix-icon="el-ksd-icon-search_22" style="width:240px" :placeholder="$t('kylinLang.common.pleaseFilter')"></el-input>
         </div>
       </div>
@@ -504,15 +478,6 @@
                 <span :class="['icon', 'el-icon-ksd-move_to_top', {'is-disabled': index === 0 && !searchName}]" @click="moveTo('top', item)"></span>
                 <span :class="['icon', 'el-icon-ksd-move_up', {'is-disabled': index === 0}]" @click="moveTo('up', item)"></span>
                 <span :class="['icon', 'el-icon-ksd-move_down', {'is-disabled': !includeDimensions[index + 1] || !includeDimensions[index + 1].isCheck}]" @click="moveTo('down', item)"></span>
-                <!-- <el-tooltip :content="$t('moveTop')" effect="dark" placement="top">
-                  <span :class="['icon', 'el-icon-ksd-move_to_top', {'is-disabled': index === 0}]" @click="moveTo('top', item)"></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="moveTo('up', item)"></span>
-                </el-tooltip>
-                <el-tooltip :content="$t('moveDown')" effect="dark" placement="top">
-                  <span :class="['icon', 'el-icon-ksd-move_down', {'is-disabled': !includeDimensions[index + 1] || !includeDimensions[index + 1].isCheck}]" @click="moveTo('down', item)"></span>
-                </el-tooltip> -->
               </template>
             </el-col>
           </el-row>
@@ -676,6 +641,7 @@ export default class AggregateModal extends Vue {
   pageSize = 50
   generateDeletedIndexes = true
   displayExcludedTables = false
+  isCatchUpLoading = false
   dataDragData = {
     width: 238,
     limit: {
@@ -738,11 +704,6 @@ export default class AggregateModal extends Vue {
     return this.selectedMeasures
   }
 
-  // 是否展示屏蔽表 checkbox
-  // get showExcludedTableCheckBox () {
-  //   return this.backUpDimensions.length ? this.backUpDimensions.filter(it => typeof it.excluded !== 'undefined' && it.excluded).length > 0 : false
-  // }
-
   // 是否存在多对多且被屏蔽的表
   get hasManyToManyAndAntiTable () {
     let flag = false
@@ -822,7 +783,6 @@ export default class AggregateModal extends Vue {
       this.isWaitingCheckCuboids[prop] = false
     }
     this.calcLoading = true
-    // this.showLoading()
     let paramsData = this.getSubmitData()
     if (paramsData.dimensions.length <= 0) {
       this.calcLoading = false
@@ -834,7 +794,6 @@ export default class AggregateModal extends Vue {
       handleSuccess(res, (data) => {
         if (data) {
           this.cuboidsInfo = objectClone(data)
-          // this.cuboidsInfo.agg_index_counts = data.agg_index_counts.reverse()
           let resultData = objectClone(data.agg_index_counts)
           // 用聚合组的唯一id 做标识
           this.cuboidsInfo.agg_index_counts = {}
@@ -860,7 +819,6 @@ export default class AggregateModal extends Vue {
         }
         this.calcLoading = false
         this.isNeedCheck = false
-        // this.hideLoading()
       })
     }, (res) => {
       this.maxCombinationNum = 0
@@ -987,7 +945,6 @@ export default class AggregateModal extends Vue {
     const aggregateData = {
       ...JSON.parse(initialAggregateData),
       ...{ measures: this.reorganizedMeasures() },
-      // id: aggregateArray.length
       id: sampleGuid()
     }
     this.aggregateStyle = []
@@ -995,7 +952,6 @@ export default class AggregateModal extends Vue {
     this.isWaitingCheckCuboids[aggregateData.id] = true
     this.isWaitingCheckAllCuboids = true
     this.aggregateStyle = []
-    // this.calcCuboids()
     this.$nextTick(() => {
       const scrollTop = this.$el.querySelector('.aggregate-modal .aggregate-dialog').parentElement.scrollTop
       this.$el.querySelector('.aggregate-modal .aggregate-dialog').parentElement.scrollTop = scrollTop + 370
@@ -1023,7 +979,6 @@ export default class AggregateModal extends Vue {
     const aggregateArray = get(this.form, 'aggregateArray')
     const copyedAggregate = {
       ...aggregateArray[aggregateIdx],
-      // id: aggregateArray.length
       id: sampleGuid()
     }
     this.setModalForm({ aggregateArray: [...aggregateArray, copyedAggregate] })
@@ -1033,7 +988,6 @@ export default class AggregateModal extends Vue {
     this.isWaitingCheckCuboids[copyedAggregate.id] = true
     this.isWaitingCheckAllCuboids = true
     this.aggregateStyle = []
-    // this.calcCuboids()
     this.$nextTick(() => {
       const detailContents = this.$el.querySelectorAll('.aggregate-modal .aggregate-dialog .aggregate-group')
       this.$el.querySelector('.aggregate-modal .aggregate-dialog').parentElement.scrollTop = detailContents[this.form.aggregateArray.length - 1].offsetTop - 100
@@ -1044,10 +998,8 @@ export default class AggregateModal extends Vue {
       const aggregateArray = get(this.form, 'aggregateArray')
       aggregateArray.splice(aggregateIdx, 1)
       this.setModalForm({ aggregateArray })
-      // this.isWaitingCheckCuboids = true
       this.isWaitingCheckAllCuboids = true
       this.aggregateStyle = []
-      // this.calcCuboids()
     })
   }
   handleAddDimensionRow (path, id) {
@@ -1058,7 +1010,6 @@ export default class AggregateModal extends Vue {
     this.setModalForm({[rootKey]: push(this.form, path, newDimensionRow)[rootKey]})
     this.isWaitingCheckCuboids[id] = true
     this.isWaitingCheckAllCuboids = true
-    // this.calcCuboids()
   }
   handleRemoveDimensionRow (path, aggregateIdx, dimensionRowIndex, id) {
     const rootKey = path.split('.')[0]
@@ -1069,7 +1020,6 @@ export default class AggregateModal extends Vue {
     }
     this.isWaitingCheckCuboids[id] = true
     this.isWaitingCheckAllCuboids = true
-    // this.calcCuboids()
   }
   beforeDestroy () {
     // 销毁组件前需要重置组件里的相关信息,以防切换模型,展开聚合组时,信息混乱
@@ -1110,7 +1060,6 @@ export default class AggregateModal extends Vue {
     if (key !== 'isCatchUp') {
       this.isWaitingCheckCuboids[id] = true
       this.isWaitingCheckAllCuboids = true
-      // this.calcCuboids()
     }
     const rootKey = key.split('.')[0]
     this.setModalForm({[rootKey]: set(this.form, key, value)[rootKey]})
@@ -1146,10 +1095,6 @@ export default class AggregateModal extends Vue {
       }
     })
   }
-  // handleAddAllIncludes (aggregateIdx, id) {
-  //   const allDimensions = this.dimensions.map(dimension => dimension.label)
-  //   this.handleInput(`aggregateArray.${aggregateIdx}.includes`, allDimensions, id)
-  // }
   handleRemoveAllIncludes (aggregateIdx, titleId, id) {
     kylinConfirm(this.$t('clearAllAggregateTip', {aggId: titleId}), {type: 'warning'}, this.$t('clearAggregateTitle')).then(() => {
       const { aggregateArray = [] } = this.form
@@ -1210,6 +1155,7 @@ export default class AggregateModal extends Vue {
     })
   }
   async handleSubmit (isCatchUp) {
+    this.isCatchUpLoading = isCatchUp
     // 该字段只有在保存并构建时才会用到,纯流模型是屏蔽保存并构建的
     const isHaveBatchSegment = this.model.model_type === 'HYBRID' ? this.model.batch_segments.length > 0 : this.model.segments.length > 0
     // 保存并全量构建时,可以直接提交构建任务,保存并增量构建时,需弹出segment list选择构建区域
@@ -1217,7 +1163,6 @@ export default class AggregateModal extends Vue {
       this.setModalForm({isCatchUp: set(this.form, 'isCatchUp', isCatchUp)['isCatchUp']})
     }
     this.isSubmit = true
-    // this.showLoading()
     try {
       if (this.checkFormVaild()) {
         const data = this.getSubmitData()
@@ -1237,7 +1182,6 @@ export default class AggregateModal extends Vue {
         if (cuboidsResult) {
           this.maxCombinationNum = cuboidsResult.max_combination_num
           this.cuboidsInfo = objectClone(cuboidsResult)
-          // this.cuboidsInfo.agg_index_counts = cuboidsResult.agg_index_counts.reverse()
           let resultData = objectClone(cuboidsResult.agg_index_counts)
           // 用聚合组的唯一id 做标识
           this.cuboidsInfo.agg_index_counts = {}
@@ -1253,7 +1197,6 @@ export default class AggregateModal extends Vue {
             this.$message.error(this.$t('maxCombinationTip'))
             this.calcLoading = false
             this.isSubmit = false
-            // this.hideLoading()
             // 操作滚动
             this.dealScrollToFirstError()
             return false
@@ -1262,7 +1205,6 @@ export default class AggregateModal extends Vue {
             this.$message.error(this.$t('maxTotalCombinationTip'))
             this.calcLoading = false
             this.isSubmit = false
-            // this.hideLoading()
             return false
           }
         }
@@ -1272,9 +1214,6 @@ export default class AggregateModal extends Vue {
         // 获取数字正常的情况下,才进行 submit
         let res = await this.submit({...data, restoreDeletedIndex: !this.generateDeletedIndexes})
         let result = await handleSuccessAsync(res)
-        // if (!isCatchUp && !this.model.segments.length) {
-        //   this.$emit('needShowBuildTips', this.model.uuid)
-        // }
         if (isCatchUp && !isHaveBatchSegment) {
           this.$emit('openBuildDialog', this.model, true)
         }
@@ -1289,10 +1228,8 @@ export default class AggregateModal extends Vue {
         }
         this.isSubmit = false
         this.handleClose(true)
-        // this.hideLoading()
       } else {
         this.isSubmit = false
-        // this.hideLoading()
       }
     } catch (e) {
       this.calcLoading = false
@@ -1301,7 +1238,6 @@ export default class AggregateModal extends Vue {
       }
       e && handleError(e)
       this.isSubmit = false
-      // this.hideLoading()
     }
   }
   confirmBuildIndexTip () {
@@ -1350,7 +1286,6 @@ export default class AggregateModal extends Vue {
     const saveText = isCatchUp ? `${this.$t('confirmTextBySaveAndBuild')}${this.model.model_type === 'HYBRID' ? this.$t('habirdModelBuildTips') : ''}` : this.$t('confirmTextBySave')
     const saveBtnText = isCatchUp ? this.$t('bulidAndSubmit') : this.$t('kylinLang.common.save')
     const ctx = this.$createElement
-    // this.generateDeletedIndexes = true // 默认 checkbox 勾选状态
     this.$nextTick(() => {
       this.chengeGenerateDeletedIndexesStatus('init', this.generateDeletedIndexes, diffResult)
     })
@@ -1376,13 +1311,11 @@ export default class AggregateModal extends Vue {
       showClose: false,
       closeOnPressEscape: false,
       showCancelButton: true,
-      // customClass: diffResult.increase_layouts > 0 && diffResult.decrease_layouts > 0 ? 'aggBuildChangeMessageAll' : 'aggBuildChangeMessageSinger',
       title: this.$t('kylinLang.common.tip')
     })
   }
   chengeGenerateDeletedIndexesStatus (type, v, diffResult) {
     this.generateDeletedIndexes = type === 'init' ? v : v.target.checked
-    // this.$set(this, 'generateDeletedIndexes', v.target.checked)
     this.$nextTick(() => {
       if (document.body.getElementsByClassName('agg-build-message-all_title').length || document.body.getElementsByClassName('agg-build-message-single_title').length) {
         if (diffResult.increase_layouts > 0 && diffResult.decrease_layouts > 0) {
diff --git a/kystudio/src/components/studio/StudioModel/ModelList/Components/ModelTitleDescription.vue b/kystudio/src/components/studio/StudioModel/ModelList/Components/ModelTitleDescription.vue
new file mode 100644
index 0000000000..8d264fd8c7
--- /dev/null
+++ b/kystudio/src/components/studio/StudioModel/ModelList/Components/ModelTitleDescription.vue
@@ -0,0 +1,217 @@
+<template>
+    <div class="model-alias-label" v-if="modelData">
+      <div class="alias">
+        <el-popover
+          ref="statusPopover"
+          popper-class="status-tooltip"
+          placement="top-start"
+          trigger="hover"
+          :disabled="!modelData.status"
+        >
+          <span v-html="$t('modelStatus_c')" />
+          <span>{{modelData.status}}</span>
+          <div v-if="modelData.status === 'WARNING' && modelData.empty_indexes_count">{{$t('emptyIndexTips')}}</div>
+          <div v-if="modelData.status === 'WARNING' && (modelData.segment_holes && modelData.segment_holes.length && modelData.model_type === 'BATCH') || (modelData.batch_segment_holes && modelData.batch_segment_holes.length && modelData.model_type === 'HYBRID')">
+            <span>{{modelData.model_type === 'HYBRID' ? $t('modelSegmentHoleTips1') : $t('modelSegmentHoleTips')}}</span><span
+              style="color:#0988DE;cursor: pointer;"
+              @click="autoFix(modelData.alias, modelData.model_type === 'HYBRID' ? modelData.batch_id : modelData.uuid, modelData.model_type === 'HYBRID' ? modelData.batch_segment_holes : modelData.segment_holes)">{{$t('seeDetail')}}</span>
+          </div>
+          <div v-if="modelData.status === 'WARNING' && (modelData.segment_holes && modelData.segment_holes.length && modelData.model_type !== 'BATCH')">
+            <span>{{$t('modelSegmentHoleTips2')}}</span>
+          </div>
+          <div v-if="modelData.status === 'WARNING' && modelData.inconsistent_segment_count">
+            <span>{{$t('modelMetadataChangedTips')}}</span><span
+              v-if="!['modelLayout', 'modelEdit'].includes(source)"
+              style="color:#0988DE;cursor: pointer;"
+              @click="openComplementSegment(model, true)">{{$t('seeDetail')}}</span>
+          </div>
+          <div v-if="modelData.status === 'OFFLINE' && modelData.forbidden_online">
+            <span>{{$t('SCD2ModalOfflineTip')}}</span>
+          </div>
+          <div v-if="modelData.status === 'OFFLINE' && !modelData.has_segments">
+            <span>{{$t('noSegmentOnlineTip')}}</span>
+          </div>
+          <div v-if="modelData.status === 'OFFLINE' && !$store.state.project.multi_partition_enabled && modelData.multi_partition_desc">
+            <span>{{$t('multilParTip')}}</span>
+          </div>
+        </el-popover>
+        <el-popover
+          ref="titlePopover"
+          placement="top-start"
+          width="250"
+          trigger="hover"
+          popper-class="title-popover-layout"
+        >
+          <div class="title-popover">
+            <p class="title ksd-mb-20">{{modelData.alias || modelData.name}}</p>
+            <div :class="['label', {'en': $lang === 'en'}]">
+              <div class="group ksd-mb-8" v-if="!onlyShowModelName"><span class="title">{{$t('kylinLang.model.ownerGrid')}}</span><span class="item">{{modelData.owner}}</span></div>
+              <div class="group"><span class="title">{{$t('description')}}</span><span class="item">{{modelData.description || '-'}}</span></div>
+            </div>
+          </div>
+        </el-popover>
+        <span :class="['filter-status', (modelData.status || 'OFFLINE')]" v-popover:statusPopover v-if="!onlyShowModelName"></span>
+        <span class="model-alias-title" @mouseenter.prevent v-popover:titlePopover>{{modelData.alias || modelData.name}}</span>
+      </div>
+      <el-tooltip class="last-modified-tooltip" effect="dark" :content="`${$t('dataLoadTime')}${modelData.gmtTime}`" placement="bottom" :disabled="hideTimeTooltip" v-if="!onlyShowModelName">
+        <span>{{getLastTime}}</span>
+      </el-tooltip>
+    </div>
+  </template>
+  
+  <script>
+  import Vue from 'vue'
+  import { Component } from 'vue-property-decorator'
+  import { transToGmtTime } from 'util/business'
+  @Component({
+    props: {
+      modelData: {
+        type: Object,
+        default () {
+          return null
+        }
+      },
+      source: {
+        type: String,
+        default: ''
+      },
+      hideTimeTooltip: {
+        type: Boolean,
+        default: false
+      },
+      onlyShowModelName: {
+        type: Boolean,
+        default: false
+      }
+    },
+    locales: {
+      en: {
+        modelStatus_c: 'Model Status:',
+        emptyIndexTips: 'This model has unbuilt indexes. Please click the build index button to build the indexes.',
+        modelSegmentHoleTips: 'This model\'s segment range has gaps in between. Empty results might be returned when querying those ranges. ',
+        modelSegmentHoleTips1: 'This model\'s batch segment range has gaps in between. Empty results might be returned when querying those ranges. ',
+        modelSegmentHoleTips2: 'This model\'s streaming data has gaps in between. Please contact technical support.',
+        modelMetadataChangedTips: 'Data in the source table was changed. The query results might be inaccurate. ',
+        seeDetail: 'View Details',
+        description: 'Description',
+        dataLoadTime: 'Last Updated Time: ',
+        SCD2ModalOfflineTip: 'This model includes non-equal join conditions (≥, <), which are not supported at the moment. Please delete those join conditions, or turn on `Support History table` in project settings.',
+        noSegmentOnlineTip: 'This model can\'t go online as it doesn\'t have segments. Models with no segment couldn\'t serve queries. Please add a segment.',
+        multilParTip: 'This model used multilevel partitioning, which are not supported at the moment. Please set subpartition as \'None\' in model partition dialog, or turn on \'Multilevel Partitioning\' in project settings.',
+        lastUpdate: 'Last Updated: '
+      }
+    }
+  })
+  export default class ModelTitleDescription extends Vue {
+    transToGmtTime = transToGmtTime
+
+    get getLastTime () {
+      return this.source !== 'modelList' ? `${this.$t('lastUpdate')}${this.transToGmtTime(this.modelData.last_modified || Date.now())}` : this.transToGmtTime(this.modelData.last_modified)
+    }
+    openComplementSegment (model, status) {
+      this.$emit('openSegment', model, status)
+    }
+  }
+  </script>
+  
+  <style lang="less">
+  @import '../../../../../assets/styles/variables.less';
+  .model-alias-label {
+    cursor: default;
+  }
+  .alias {
+    font-weight: bold;
+    line-height: 20px;
+    width: 100%;
+    margin-top: 6px;
+    cursor: default;
+    .filter-status {
+      position: relative;
+      top: 6px;
+      border-radius: 100%;
+      width: 10px;
+      height: 10px;
+      display: inline-block;
+      margin-right: 10px;
+      cursor: default;
+      &.ONLINE {
+        background-color: @color-success;
+      }
+      &.OFFLINE {
+        background-color: @ke-color-info-secondary;
+      }
+      &.BROKEN {
+        background-color: @color-danger;
+      }
+      &.WARNING {
+        background-color: @color-warning;
+      }
+    }
+    .model-alias-title {
+      max-width: 100%;
+      display: inline-block;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      white-space: nowrap\0 !important;
+    }
+  }
+  .status-tooltip {
+    min-width: unset;
+    transform: translate(-3px, 0);
+    .popper__arrow {
+      left: 8px !important;
+    }
+  }
+  .title-popover-layout {
+    font-size: 14px;
+    word-break: break-all;
+    .title {
+      color: @text-title-color;
+      font-weight: bold;
+    }
+    .label {
+      display: inline-block;
+      .group {
+        color: @text-disabled-color;
+        // text-align: right;
+        margin-right: 15px;
+        .title {
+          color: @text-disabled-color;
+          display: inline-block;
+          min-width: 40px;
+          word-break: break-all;
+          text-align: right;
+          margin-right: 10px;
+        }
+        .item {
+          display: inline-block;
+          max-width: 180px;
+          word-break: break-all;
+          vertical-align: top;
+        }
+      }
+      &.en {
+        .title {
+          min-width: 80px;
+        }
+        .item {
+          max-width: 140px;
+        }
+      }
+    }
+  }
+  .last-modified-tooltip {
+    min-width: unset;
+    margin-top: 3px;
+    margin-left: 15px;
+    color: #8B99AE;
+    font-size: 12px;
+    line-height: 20px;
+    display: block;
+    cursor: default;
+    .popper__arrow {
+      left: 5px !important;
+    }
+  }
+  </style>
\ No newline at end of file
diff --git a/kystudio/src/components/studio/StudioModel/ModelList/GuideModal/GuideModal.vue b/kystudio/src/components/studio/StudioModel/ModelList/GuideModal/GuideModal.vue
index dae453643f..a9d44fc9d4 100644
--- a/kystudio/src/components/studio/StudioModel/ModelList/GuideModal/GuideModal.vue
+++ b/kystudio/src/components/studio/StudioModel/ModelList/GuideModal/GuideModal.vue
@@ -132,7 +132,7 @@ export default class GuideModal extends Vue {
   }
   .dim-meas-block {
     position: absolute;
-    top:86px;
+    top: 120px;
     right: 0px;
     &.is-show-global-alter {
       top:142px;
diff --git a/kystudio/src/components/studio/StudioModel/ModelList/ModelActions/modelActions.vue b/kystudio/src/components/studio/StudioModel/ModelList/ModelActions/modelActions.vue
index aefb347cf4..db0ff9c2f4 100644
--- a/kystudio/src/components/studio/StudioModel/ModelList/ModelActions/modelActions.vue
+++ b/kystudio/src/components/studio/StudioModel/ModelList/ModelActions/modelActions.vue
@@ -47,6 +47,7 @@
           type="primary"
           text>{{buildText}}</el-button>
       </common-tip>
+      <span class="divide-line" v-if="buildText">|</span>
       <common-tip :content="$t('kylinLang.common.moreActions')" :disabled="!!moreText" v-if="datasourceActions.includes('modelActions') || modelActions.includes('purge') || modelActions.includes('exportTDS')">
         <el-dropdown @command="(command) => {handleCommand(command, currentModel)}" :id="currentModel.name" trigger="click" >
           <span class="el-dropdown-link" >
@@ -270,7 +271,8 @@ import locales from './locales'
       purgeModel: 'PURGE_MODEL',
       disableModel: 'DISABLE_MODEL',
       enableModel: 'ENABLE_MODEL',
-      delModel: 'DELETE_MODEL'
+      delModel: 'DELETE_MODEL',
+      exportValidation: 'VALIDATE_EXPORT_TDS'
     }),
     ...mapActions('DetailDialogModal', {
       callGlobalDetailDialog: 'CALL_MODAL'
@@ -641,24 +643,34 @@ export default class ModelActions extends Vue {
 
   handlerExportTDS () {
     const { uuid, model_id = uuid } = this.currentExportTDSModel
-    const data = {
-      project: this.currentSelectedProject,
-      export_as: this.exportTDSConnectionType,
-      element: this.exportTDSType,
-      server_host: window.location.hostname,
-      server_port: window.location.port
-    }
-    let params = ''
-    Object.keys(data).forEach(item => {
-      params += `${item}=${data[item]}&`
+    this.exportValidation({modelId: model_id, project: this.currentSelectedProject}).then(async (res) => {
+      try {
+        const value = await handleSuccessAsync(res)
+        if (!value) return
+        const data = {
+          project: this.currentSelectedProject,
+          export_as: this.exportTDSConnectionType,
+          element: this.exportTDSType,
+          server_host: window.location.hostname,
+          server_port: window.location.port
+        }
+        let params = ''
+        Object.keys(data).forEach(item => {
+          params += `${item}=${data[item]}&`
+        })
+        const dom = document.createElement('a')
+        dom.href = `${location.protocol}//${location.host}${apiUrl}models/${model_id}/export?${params}`
+        dom.download = true
+        document.body.appendChild(dom)
+        dom.click()
+        document.body.removeChild(dom)
+        this.closeExportTDSDialog()
+      } catch (e) {
+        handleError(e)
+      }
+    }).catch((e) => {
+      handleError(e)
     })
-    const dom = document.createElement('a')
-    dom.href = `${location.protocol}//${location.host}${apiUrl}models/${model_id}/export?${params}`
-    dom.download = true
-    document.body.appendChild(dom)
-    dom.click()
-    document.body.removeChild(dom)
-    this.closeExportTDSDialog()
   }
 
   // 关闭导出 TDS 弹窗
@@ -682,10 +694,17 @@ export default class ModelActions extends Vue {
   .tip_box {
     line-height: inherit !important;
     vertical-align: inherit !important;
+    display: inline-block;
   }
   .item {
     cursor: pointer;
   }
+  .divide-line {
+    display: inline-block;
+    width: 1px;
+    background: #E6EBF4;
+    margin-right: 8px;
+  }
   .icon-item {
     font-size: 22px;
     vertical-align: middle;
diff --git a/kystudio/src/components/studio/StudioModel/ModelList/ModelAggregate/index.vue b/kystudio/src/components/studio/StudioModel/ModelList/ModelAggregate/index.vue
index cb64dc4195..bcc4f70f4a 100644
--- a/kystudio/src/components/studio/StudioModel/ModelList/ModelAggregate/index.vue
+++ b/kystudio/src/components/studio/StudioModel/ModelList/ModelAggregate/index.vue
@@ -58,20 +58,13 @@
                   </el-dropdown-item>
                 </el-dropdown-menu>
               </el-dropdown>
-              <!-- <el-tooltip :content="$t('disabledBuildIndexTips')" :disabled="checkedList.length==0 || (checkedList.length>0&&!isHaveLockedIndex)">
-                <div class="ksd-fleft"> -->
                 <el-button icon="el-ksd-icon-build_index_22" :disabled="!checkedList.length || isHaveLockedIndex" text type="primary" class="ksd-ml-2 ksd-fleft" v-if="datasourceActions.includes('buildIndex') && !isRealTimeMode" @click="complementedIndexes('batchIndexes')">{{$t('buildIndex')}}</el-button>
-                <!-- </div>
-              </el-tooltip> -->
               <template v-if="isRealTimeMode">
                 <el-tooltip placement="top" :content="!indexUpdateEnabled ? $t('refuseRemoveIndexTip') : $t('disabledDelBaseIndexTips')" v-if="datasourceActions.includes('delAggIdx') && (isDisableDelBaseIndex || !indexUpdateEnabled)">
                   <div class="ksd-fleft">
                     <el-button v-if="datasourceActions.includes('delAggIdx') && (isDisableDelBaseIndex || !indexUpdateEnabled)" :disabled="isDisableDelBaseIndex || !indexUpdateEnabled" type="primary" icon="el-ksd-icon-table_delete_22" @click="removeIndexes" text>{{$t('kylinLang.common.delete')}}</el-button>
                   </div>
                 </el-tooltip>
-                <!-- <common-tip :content="$t('refuseRemoveIndexTip')" v-if="datasourceActions.includes('delAggIdx') && !indexUpdateEnabled&&checkedList.length>0">
-                  <el-button v-if="datasourceActions.includes('delAggIdx') && !indexUpdateEnabled" :disabled="!indexUpdateEnabled" type="primary" icon="el-ksd-icon-table_delete_22" @click="removeIndexes" class="ksd-fleft" text>{{$t('kylinLang.common.delete')}}</el-button>
-                </common-tip> -->
                 <el-button v-if="datasourceActions.includes('delAggIdx') && !isDisableDelBaseIndex &&  indexUpdateEnabled" :disabled="!checkedList.length" type="primary" icon="el-ksd-icon-table_delete_22" class="ksd-fleft" @click="removeIndexes" text>{{$t('kylinLang.common.delete')}}</el-button>
               </template>
               <template v-else>
@@ -124,7 +117,7 @@
               <div class="filter-tags-layout"><el-tag size="mini" closable v-for="(item, index) in filterTags" :key="index" @close="handleClose(item)">{{`${$t(item.source)}:${$t(item.label)}`}}</el-tag></div>
               <span class="clear-all-filters" @click="clearAllTags">{{$t('clearAll')}}</span>
             </div>
-            <div class="index-table-list" :class="{'is-show-tips' :isRealTimeMode&&isShowRealTimeModelActionTips, 'is-show-tab-button': showModelTypeSwitch, 'is-show-tips--tab-button': isRealTimeMode&&isShowRealTimeModelActionTips&&showModelTypeSwitch}">
+            <div class="index-table-list" :class="{'is-show-filter': filterTags.length, 'is-show-tips' :isRealTimeMode&&isShowRealTimeModelActionTips, 'is-show-tab-button': showModelTypeSwitch, 'is-show-tips--tab-button': isRealTimeMode&&isShowRealTimeModelActionTips&&showModelTypeSwitch}">
               <el-table
                 ref="indexesTable"
                 :data="indexDatas"
@@ -941,6 +934,9 @@ export default class ModelAggregate extends Vue {
   .index-table-list {
     max-height: 90%;
     overflow: auto;
+    &.is-show-filter {
+      max-height: calc(~'90% - 36px');
+    }
     &.is-show-tips {
       max-height: calc(~'90% - 45px');
     }
@@ -950,6 +946,15 @@ export default class ModelAggregate extends Vue {
     &.is-show-tips--tab-button {
       max-height: calc(~'90% - 45px - 36px');
     }
+    &.is-show-filter.is-show-tips {
+      max-height: calc(~'90% - 36px - 45px');
+    }
+    &.is-show-filter.is-show-tab-button {
+      max-height: calc(~'90% - 36px - 36px');
+    }
+    &.is-show-filter.is-show-tips--tab-button {
+      max-height: calc(~'90% - 36px - 45px - 36px');
+    }
   }
   .indexes-table {
     .el-table__empty-img-text {
diff --git a/kystudio/src/components/studio/StudioModel/ModelList/ModelAggregateView/AggAdvancedModal/index.vue b/kystudio/src/components/studio/StudioModel/ModelList/ModelAggregateView/AggAdvancedModal/index.vue
index bdffa8632c..c1a999606b 100644
--- a/kystudio/src/components/studio/StudioModel/ModelList/ModelAggregateView/AggAdvancedModal/index.vue
+++ b/kystudio/src/components/studio/StudioModel/ModelList/ModelAggregateView/AggAdvancedModal/index.vue
@@ -38,11 +38,6 @@
               <el-col :span="3">
                 <div class="action-list" @click="toggleColumnShard(col)" v-if="!(sortCount >= 9 && getRowIndex(col, 'fullName') + 1 > 9)">
                   <i class="el-icon-success" :class="{active: col.isShared}"></i>
-                  <!-- <span class="ky-dot-tag" v-if="col.isUsed" :class="{'no-sorted': !col.isSorted}">{{col.isSorted ? getRowIndex(col, 'fullName') + 1 : sortCount + 1}}</span> -->
-                  <!-- <span class="up-down" :class="{hide: searchColumn}">
-                    <i v-visible="col.isUsed && col.isSorted && !checkIsTopSort(col)" @click.stop="upRow(col)" class="el-icon-ksd-arrow_up"></i>
-                    <i v-visible="col.isUsed && col.isSorted && !checkIsBottomSort(col)" @click.stop="downRow(col)" class="el-icon-ksd-arrow_down"></i>
-                  </span> -->
                 </div>
               </el-col>
             </el-row>
diff --git a/kystudio/src/components/studio/StudioModel/ModelList/ModelBuildModal/build.vue b/kystudio/src/components/studio/StudioModel/ModelList/ModelBuildModal/build.vue
index a16efe4a3d..8a63c62ea3 100644
--- a/kystudio/src/components/studio/StudioModel/ModelList/ModelBuildModal/build.vue
+++ b/kystudio/src/components/studio/StudioModel/ModelList/ModelBuildModal/build.vue
@@ -20,15 +20,6 @@
           show-icon>
         </el-alert>
         <div class="ksd-title-label-small ksd-mb-10">{{$t('chooseBuildType')}}</div>
-        <!-- <div>
-          <el-radio-group v-model="buildOrComplete" class="ksd-mb-10">
-            <el-radio label="build">{{$t('build')}}</el-radio>
-            <common-tip :content="$t('unableComplete')" v-if="!modelDesc.empty_indexes_count">
-              <el-radio :disabled="!modelDesc.empty_indexes_count" class="ksd-ml-10" label="complete">{{$t('complete')}}</el-radio>
-            </common-tip>
-            <el-radio v-else label="complete">{{$t('complete')}}</el-radio>
-          </el-radio-group>
-        </div> -->
         <el-select v-model="buildType" class="ksd-mb-5" @change="handChangeBuildType" v-if="buildOrComplete == 'build'" :disabled="!datasourceActions.includes('changeBuildType')">
           <el-option :label="$t('incremental')" value="incremental"></el-option>
           <el-option :label="$t('fullLoad')" v-if="!isStreamModel" value="fullLoad"></el-option>
@@ -54,7 +45,6 @@
               <el-col :span="12">
                 <el-tooltip effect="dark" :content="$t('disableChangePartitionTips')" :disabled="!isNotBatchModel" placement="bottom">
                   <el-select :disabled="isLoadingNewRange || !datasourceActions.includes('changePartition') || isNotBatchModel" v-model="partitionMeta.table" @change="partitionTableChange" :placeholder="$t('kylinLang.common.pleaseSelectOrSearch')" style="width:100%">
-                    <!-- <el-option :label="$t('noPartition')" value=""></el-option> -->
                     <el-option :label="t.alias" :value="t.alias" v-for="t in partitionTables" :key="t.alias">{{t.alias}}</el-option>
                   </el-select>
                 </el-tooltip>
@@ -91,11 +81,7 @@
                     <el-option-group>
                       <el-option v-if="prevPartitionMeta.format.indexOf(dateFormatsOptions) === -1&&prevPartitionMeta.format" :label="prevPartitionMeta.format" :value="prevPartitionMeta.format"></el-option>
                       <el-option :label="f.label" :value="f.value" v-for="f in dateFormatsOptions" :key="f.label"></el-option>
-                      <!-- <el-option label="" value="" v-if="partitionMeta.column && timeDataType.indexOf(getColumnInfo(partitionMeta.column).datatype)===-1"></el-option> -->
                     </el-option-group>
-                    <!-- <el-option-group>
-                      <el-option :label="f.label" :value="f.value" v-for="f in dateTimestampFormats" :key="f.label"></el-option>
-                    </el-option-group> -->
                   </el-select>
                 </el-tooltip>
               </el-col>
@@ -165,18 +151,7 @@
           </div>
           <div class="ksd-title-label-small ksd-mb-10">{{$t('addRangeTitle')}}</div>
           <el-form :model="modelBuildMeta" ref="buildForm" :rules="rules" label-position="top">
-            <!-- <div class="ky-list-title ksd-mt-14">{{$t('buildRange')}}</div> -->
-            <!-- <el-form-item prop="isLoadExisted" class="ksd-mt-10 ksd-mb-2">
-              <el-radio class="font-medium" v-model="modelBuildMeta.isLoadExisted" :label="true">
-                {{$t('loadExistingData')}}
-              </el-radio>
-              <div class="item-desc">{{$t('loadExistingDataDesc')}}</div>
-            </el-form-item> -->
             <el-form-item :class="{'is-error': isShowErrorSegments}" :rule="modelBuildMeta.isLoadExisted ? [] : [{required: true, trigger: 'blur', message: this.$t('dataRangeValValid')}]">
-              <!-- <el-radio class="font-medium" v-model="modelBuildMeta.isLoadExisted" :label="false">
-                {{$t('customLoadRange')}}
-              </el-radio>
-              <br/> -->
               <p class="ksd-pt-0 ksd-fs-12 segment-tips"><i class="el-icon-ksd-alert ksd-mr-5 alert-icon"></i>{{$t('segmentTips')}}</p>
               <div class="ky-no-br-space" style="height:32px;">
                 <el-date-picker
@@ -289,9 +264,9 @@
         </div> -->
         <el-button @click="closeModal(false)" size="medium">{{$t('kylinLang.common.cancel')}}</el-button>
         <template v-if="isAddSegment">
-          <el-button type="primary" :loading="btnLoading" @click="setbuildModel(false, 'onlySave')" :disabled="incrementalDisabled || disableFullLoad" size="medium">{{$t('kylinLang.common.save')}}</el-button>
-          <el-button type="primary" :loading="btnLoading" v-if="modelDesc.total_indexes && !multiPartitionEnabled" @click="setbuildModel(true)" :disabled="incrementalDisabled || disableFullLoad" size="medium">{{$t('saveAndBuild')}}</el-button>
-          <el-button type="primary" :loading="btnLoading" v-else-if="!multiPartitionEnabled" @click="saveAndAddIndex" :disabled="incrementalDisabled || disableFullLoad" size="medium">{{$t('saveAndAddIndex')}}</el-button>
+          <el-button type="primary" :loading="btnLoading&&!isBuildLoading&&!isWillAddIndex" @click="setbuildModel(false, 'onlySave')" :disabled="incrementalDisabled || disableFullLoad || btnLoading&&(isBuildLoading || isWillAddIndex)" size="medium">{{$t('kylinLang.common.save')}}</el-button>
+          <el-button type="primary" :loading="btnLoading&&isBuildLoading&&!isWillAddIndex" v-if="modelDesc.total_indexes && !multiPartitionEnabled" @click="setbuildModel(true)" :disabled="incrementalDisabled || disableFullLoad || btnLoading&&!isBuildLoading" size="medium">{{$t('saveAndBuild')}}</el-button>
+          <el-button type="primary" :loading="btnLoading&&!isBuildLoading&&isWillAddIndex" v-else-if="!multiPartitionEnabled" @click="saveAndAddIndex" :disabled="incrementalDisabled || disableFullLoad || btnLoading&&!isWillAddIndex" size="medium">{{$t('saveAndAddIndex')}}</el-button>
         </template>
         <template v-else>
           <el-button type="primary" :loading="btnLoading" @click="setbuildModel(true)" :disabled="incrementalDisabled || disableFullLoad || duplicateValueError" size="medium">{{$t(buildType)}}</el-button>
@@ -376,11 +351,7 @@
       dataRangeVal: [],
       isLoadExisted: false
     }
-    rules = {
-      // dataRangeVal: [{
-      //   validator: this.validateRange, trigger: 'blur'
-      // }]
-    }
+    rules = {}
     loadRangeDateError = ''
     isShowRangeDateError = false
     isShowErrorSegments = false
@@ -426,6 +397,7 @@
     isExpandFormatRule = false
     timestamp = Date.now().toString(32)
     secondStoragePartitionTips = false
+    isBuildLoading = false
 
     @Watch('buildType')
     changeBuildType (newVal, oldVal) {
@@ -484,7 +456,7 @@
       return !(this.partitionMeta.table && this.partitionMeta.column && this.partitionMeta.format)
     }
     get incrementalDisabled () {
-      return !(this.partitionMeta.table && this.partitionMeta.column && this.partitionMeta.format && this.modelBuildMeta.dataRangeVal.length) && this.buildType === 'incremental'
+      return !(this.partitionMeta.table && this.partitionMeta.column && this.partitionMeta.format && this.modelBuildMeta.dataRangeVal.length && this.modelBuildMeta.dataRangeVal[0] && this.modelBuildMeta.dataRangeVal[1]) && this.buildType === 'incremental'
     }
     get partitionFormat () {
       if (this.partitionMeta.format === 'TIMESTAMP SECOND') {
@@ -907,6 +879,7 @@
       this.setbuildModel(false)
     }
     async setbuildModel (isBuild, type) {
+      this.isBuildLoading = isBuild
       this.btnLoading = true
       try {
         if (this.buildType === 'incremental' && this.buildOrComplete === 'build') {
@@ -940,7 +913,6 @@
             const partition_desc = {}
             if (typeof this.modelDesc.available_indexes_count === 'number' && this.modelDesc.available_indexes_count > 0) {
               if (this.prevPartitionMeta.table !== this.partitionMeta.table || this.prevPartitionMeta.column !== this.partitionMeta.column || this.prevPartitionMeta.format !== this.partitionMeta.format || this.prevPartitionMeta.multiPartition !== this.partitionMeta.multiPartition) {
-                // await kylinConfirm(this.$t('changeSegmentTip1', {tableColumn: `${this.partitionMeta.table}.${this.partitionMeta.column}`, dateType: this.partitionMeta.format, modelName: this.modelDesc.name}), '', this.$t('kylinLang.common.tip'))
                 try {
                   await kylinConfirm(this.$t('changeSegmentTips'), {confirmButtonText: this.$t('kylinLang.common.save'), type: 'warning', dangerouslyUseHTMLString: true}, this.$t('kylinLang.common.tip'))
                 } catch (e) {
@@ -1036,7 +1008,6 @@
           })
         } else if (this.buildType === 'fullLoad' && this.buildOrComplete === 'build') {
           if (this.modelDesc && this.modelDesc.partition_desc && this.modelDesc.partition_desc.partition_date_column) {
-            // await kylinConfirm(this.$t('changeBuildTypeTipsConfirm', {modelName: this.modelDesc.name}), '', this.$t('kylinLang.common.tip'))
             try {
               await kylinConfirm(this.$t('changeSegmentTips'), {confirmButtonText: this.$t('kylinLang.common.save'), type: 'warning', dangerouslyUseHTMLString: true}, this.$t('kylinLang.common.tip'))
             } catch (e) {
@@ -1054,7 +1025,6 @@
             project: this.currentSelectedProject
           }).then(async () => {
             this.btnLoading = false
-            // await this.$emit('refreshModelList')
             if (this.isWillAddIndex) {
               this.$emit('isWillAddIndex')
             } else {
@@ -1147,7 +1117,6 @@
         let data = await handleSuccessAsync(res)
         this.handleBuildIndexTip(data)
         this.closeModal(true)
-        // this.$emit('refreshModelList')
       } catch (e) {
         handleError(e)
       } finally {
diff --git a/kystudio/src/components/studio/StudioModel/ModelList/ModelLayout/modelLayout.vue b/kystudio/src/components/studio/StudioModel/ModelList/ModelLayout/modelLayout.vue
index bc43ac69d6..dc8838e576 100644
--- a/kystudio/src/components/studio/StudioModel/ModelList/ModelLayout/modelLayout.vue
+++ b/kystudio/src/components/studio/StudioModel/ModelList/ModelLayout/modelLayout.vue
@@ -2,7 +2,8 @@
   <div class="model-layout" :key="randomKey">
     <div class="header-layout">
       <div class="title"><el-button type="primary" text icon-button-mini icon="el-ksd-icon-arrow_left_16" size="small" @click="jumpBack"></el-button>
-        <span class="model-name"><span class="name ksd-fs-16">{{modelName}}</span><el-button class="ksd-ml-2" type="primary" text @click.stop="showModelList = !showModelList" icon-button-mini icon="el-ksd-icon-arrow_down_16" size="small"></el-button></span>
+        <model-title-description :modelData="currentModelRow" source="modelLayout" v-if="currentModelRow" hideTimeTooltip />
+        <el-button class="ksd-ml-8" type="primary" text @click.stop="showModelList = !showModelList" icon-button-mini icon="el-ksd-icon-arrow_down_16" size="small"></el-button>
         <div class="model-filter-list" v-if="showModelList">
           <div class="search-bar"><el-input class="search-model-input" v-model="searchModelName" size="small" :placeholder="$t('kylinLang.common.pleaseInput')" prefix-icon="el-ksd-icon-search_22" v-global-key-event.enter.debounce="searchModel" @clear="searchModel()"></el-input></div>
           <div class="model-list" v-loading="showSearchResult">
@@ -28,11 +29,11 @@
         :editText="$t('modelEditAction')"
         :buildText="$t('modelBuildAction')"
         :moreText="$t('moreAction')"
-        other-icon="el-ksd-icon-more_with_border_22"
+        other-icon="el-ksd-n-icon-more-outlined"
       />
     </div>
     <el-tabs class="el-tabs--default model-detail-tabs" tab-position="left" v-if="currentModelRow" v-model="currentModelRow.tabTypes" :key="$lang">
-      <el-tab-pane class="tab-pane-item" :label="$t('overview')" name="overview">
+      <el-tab-pane :class="['tab-pane-item']" :label="$t('overview')" name="overview">
         <ModelOverview
           v-if="currentModelRow.tabTypes === 'overview'"
           :ref="`$model-overview-${currentModelRow.uuid}`"
@@ -154,6 +155,7 @@ import ModelRenameModal from '../ModelRenameModal/rename.vue'
 import ModelCloneModal from '../ModelCloneModal/clone.vue'
 import ModelPartition from '../ModelPartition/index.vue'
 import ModelStreamingJob from '../ModelStreamingJob/ModelStreamingJob.vue'
+import ModelTitleDescription from '../Components/ModelTitleDescription'
 
 @Component({
   beforeRouteEnter (to, from, next) {
@@ -222,7 +224,8 @@ import ModelStreamingJob from '../ModelStreamingJob/ModelStreamingJob.vue'
     ModelRenameModal,
     ModelCloneModal,
     ModelPartition,
-    ModelStreamingJob
+    ModelStreamingJob,
+    ModelTitleDescription
   },
   locales
 })
@@ -287,15 +290,6 @@ export default class ModelLayout extends Vue {
   }
 
   selectModel ({model, ...args}) {
-    // this.$router.replace({name: 'ModelDetails', params: {modelName: model.alias, searchModelName: this.searchModelName, ...args}})
-    // this.$nextTick(() => {
-    //   this.forceUpdateRoute()
-    // })
-    // if (model.status && model.status === 'BROKEN') return
-    // this.$router.push({name: 'refresh'})
-    // this.$nextTick(() => {
-    //   this.$router.replace({name: 'ModelDetails', params: {modelName: model.alias, searchModelName: this.searchModelName, ...args}, query: {modelPageOffest: this.modelPageOffest}})
-    // })
     let data = {searchModelName: this.searchModelName, ...args}
     let modelData = this.modelList.filter(it => it.alias === this.modelName)
     this.modelName = model.alias
@@ -505,15 +499,37 @@ export default class ModelLayout extends Vue {
       padding: 0 14px;
       box-sizing: border-box;
       line-height: 56px;
-      // box-shadow: 1px 1px 4px #ccc;
       border-bottom: 1px solid #ECF0F8;
-      background-color: @ke-background-color-secondary;
+      background-color: @fff;
       position: relative;
-      .title {
+      .model-alias-label {
         display: inline-block;
-        // height: 100%;
+        height: 100%;
+        margin-top: 10px;
+        margin-left: 8px;
+        max-width: 270px;
+        .alias {
+          margin-top: 1px;
+          max-width: 300px;
+          .model-alias-title {
+            max-width: 90%;
+          }
+          .filter-status {
+            top: -4px;
+            margin-right: 2px;
+          }
+        }
+        .last-modified-tooltip {
+          margin-top: -4px;
+          font-weight: initial;
+        }
+      }
+      .title {
         font-weight: 600;
         line-height: 23px\0;
+        display: flex;
+        align-items: center;
+        height: 100%;
         .el-button {
           vertical-align: middle;
         }
@@ -533,6 +549,8 @@ export default class ModelLayout extends Vue {
         right: 10px;
         transform: translate(0, -50%);
         line-height: 23px;
+        display: flex;
+        align-items: center;
         .el-dropdown {
           position: inherit;
         }
@@ -545,7 +563,7 @@ export default class ModelLayout extends Vue {
       .el-tabs__item.is-left {
         padding: 0 20px;
       }
-      .el-tabs__nav-wrap {
+      .el-tabs__nav-wrap:not(.is-top) {
         border-right: 1px solid @ke-border-divider-color;
       }
       .segment-actions {
@@ -567,7 +585,7 @@ export default class ModelLayout extends Vue {
     .el-tabs--default {
       height: calc(~'100% - 56px');
       .el-tabs__header {
-        margin: 0 0 16px;
+        margin: 0 1px 16px;
         .el-tabs__nav-wrap {
           width: 144px;
         }
@@ -578,6 +596,9 @@ export default class ModelLayout extends Vue {
           padding: 24px;
           box-sizing: border-box;
         }
+        #pane-overview {
+          padding: 24px 0 0 0;
+        }
         .el-tab-pane {
           height: 100%;
         }
@@ -610,13 +631,15 @@ export default class ModelLayout extends Vue {
       position: absolute;
       padding: 0 0 14px 0;
       box-sizing: border-box;
-      z-index: 10;
+      z-index: 11;
       line-height: 56px;
       background: @ke-background-color-white;
       box-shadow: 0px 2px 8px rgba(50, 73, 107, 24%);
       border-radius: 6px;
       border: 1px solid @ke-border-divider-color;
       max-width: 240px;
+      top: 56px;
+      left: 100px;
       .search-bar {
         padding: 0 16px;
         box-sizing: border-box;
diff --git a/kystudio/src/components/studio/StudioModel/ModelList/ModelOverview/ModelOverview.vue b/kystudio/src/components/studio/StudioModel/ModelList/ModelOverview/ModelOverview.vue
index 451cddd7cc..446ed951e7 100644
--- a/kystudio/src/components/studio/StudioModel/ModelList/ModelOverview/ModelOverview.vue
+++ b/kystudio/src/components/studio/StudioModel/ModelList/ModelOverview/ModelOverview.vue
@@ -1,7 +1,7 @@
 <template>
-  <el-tabs class="model-overview" v-model="activeTab" v-if="model">
+  <el-tabs :class="['model-overview', {'er-overview': activeTab === 'erDiagram'}]" v-model="activeTab" v-if="model">
     <el-tab-pane :label="$t('erDiagram')" name="erDiagram">
-      <ModelERDiagram ref="$er-diagram" is-show-full-screen :model="model" />
+      <ModelERDiagram ref="$er-diagram" :model="model" />
     </el-tab-pane>
     <el-tab-pane :label="$t('dimension')" name="dimension">
       <ModelDimensionList :model="model" />
@@ -55,8 +55,8 @@ export default class ModelOverview extends Vue {
 @import '../../../../../assets/styles/variables.less';
 
 .model-overview {
-  // height: 100%;
-  height: calc(~'100% - 32px');
+  height: 100%;
+  // height: calc(~'100% - 32px');
   // margin: 15px;
   box-shadow: none;
   // border:1px solid rgba(245,245,245,1);
@@ -68,8 +68,12 @@ export default class ModelOverview extends Vue {
     //   font-weight: normal;
     // }
   }
+  .el-tabs__header {
+    margin: 0 0 1px !important;
+  }
   > .el-tabs__content {
     padding: 0;
+    height: calc(~'100% - 38px') !important;
   }
   > .el-tabs__content > .el-tab-pane {
     // padding: 20px;
@@ -80,12 +84,30 @@ export default class ModelOverview extends Vue {
   .el-tabs__nav-scroll {
     background-color: @ke-background-color-white;
   }
+  &.er-overview {
+    .el-tabs__header.is-top {
+      padding-left: 24px;
+    }
+    #pane-erDiagram {
+      overflow: hidden;
+    }
+  }
   .model-er-diagram {
     // margin: -20px;
     // height: calc(~'100% + 40px');
     // width: calc(~'100% + 40px');
     height: 100%;
     width: 100%;
+    background-color: #F8F9FB;
+    margin-left: 1px;
+    overflow: hidden;
+  }
+  .model-dimension-list, .model-measure-list {
+    margin-top: 16px;
   }
 }
+.model-overview:not(.er-overview) {
+  padding: 0 24px 24px 24px;
+  box-sizing: border-box;
+}
 </style>
diff --git a/kystudio/src/components/studio/StudioModel/ModelList/index.vue b/kystudio/src/components/studio/StudioModel/ModelList/index.vue
index d9ca8f9c15..15e664c761 100644
--- a/kystudio/src/components/studio/StudioModel/ModelList/index.vue
+++ b/kystudio/src/components/studio/StudioModel/ModelList/index.vue
@@ -106,61 +106,7 @@
           prop="alias"
           :label="modelTableTitle">
           <template slot-scope="scope">
-            <div class="alias">
-              <el-popover
-                popper-class="status-tooltip"
-                placement="top-start"
-                trigger="hover">
-                <template slot="reference">
-                  <span :class="['filter-status', scope.row.status]"></span>
-                </template>
-                <span v-html="$t('modelStatus_c')" />
-                <span>{{scope.row.status}}</span>
-                <div v-if="scope.row.status === 'WARNING' && scope.row.empty_indexes_count">{{$t('emptyIndexTips')}}</div>
-                <div v-if="scope.row.status === 'WARNING' && (scope.row.segment_holes && scope.row.segment_holes.length && scope.row.model_type === 'BATCH') || (scope.row.batch_segment_holes && scope.row.batch_segment_holes.length && scope.row.model_type === 'HYBRID')">
-                  <span>{{scope.row.model_type === 'HYBRID' ? $t('modelSegmentHoleTips1') : $t('modelSegmentHoleTips')}}</span><span
-                    style="color:#0988DE;cursor: pointer;"
-                    @click="autoFix(scope.row.alias, scope.row.model_type === 'HYBRID' ? scope.row.batch_id : scope.row.uuid, scope.row.model_type === 'HYBRID' ? scope.row.batch_segment_holes : scope.row.segment_holes)">{{$t('seeDetail')}}</span>
-                </div>
-                <div v-if="scope.row.status === 'WARNING' && (scope.row.segment_holes && scope.row.segment_holes.length && scope.row.model_type !== 'BATCH')">
-                  <span>{{$t('modelSegmentHoleTips2')}}</span>
-                </div>
-                <div v-if="scope.row.status === 'WARNING' && scope.row.inconsistent_segment_count">
-                  <span>{{$t('modelMetadataChangedTips')}}</span><span
-                    style="color:#0988DE;cursor: pointer;"
-                    @click="openComplementSegment(scope.row, true)">{{$t('seeDetail')}}</span>
-                </div>
-                <div v-if="scope.row.status === 'OFFLINE' && scope.row.forbidden_online">
-                  <span>{{$t('SCD2ModalOfflineTip')}}</span>
-                </div>
-                <div v-if="scope.row.status === 'OFFLINE' && !scope.row.has_segments">
-                  <span>{{$t('noSegmentOnlineTip')}}</span>
-                </div>
-                <div v-if="scope.row.status === 'OFFLINE' && !$store.state.project.multi_partition_enabled && scope.row.multi_partition_desc">
-                  <span>{{$t('multilParTip')}}</span>
-                </div>
-              </el-popover>
-              <el-popover
-                ref="titlePopover"
-                placement="top-start"
-                width="250"
-                trigger="hover"
-                popper-class="title-popover-layout"
-              >
-                <div class="title-popover">
-                  <p class="title ksd-mb-20">{{scope.row.alias}}</p>
-                  <div :class="['label', {'en': $lang === 'en'}]">
-                    <div class="group ksd-mb-8"><span class="title">{{$t('kylinLang.model.ownerGrid')}}</span><span class="item">{{scope.row.owner}}</span></div>
-                    <div class="group"><span class="title">{{$t('description')}}</span><span class="item">{{scope.row.description || '-'}}</span></div>
-                  </div>
-                </div>
-              </el-popover>
-              <span class="model-alias-title" @mouseenter.prevent v-popover:titlePopover>{{scope.row.alias}}</span>
-            </div>
-            <el-tooltip class="last-modified-tooltip" effect="dark" :content="`${$t('dataLoadTime')}${scope.row.gmtTime}`" placement="bottom">
-              <span>{{scope.row.gmtTime}}</span>
-            </el-tooltip>
-
+            <model-title-description :modelData="scope.row" @openSegment="openComplementSegment" source="modelList" />
             <!-- 工具栏 -->
             <model-actions :currentModel="scope.row" @loadModelsList="loadModelsList"/>
 
@@ -178,7 +124,7 @@
                 @after-enter="(e) => afterPoppoverEnter(e, scope.row)"
                 popper-class="er-popover-layout"
               >
-                <div class="model-ER-layout"><ModelERDiagram v-if="scope.row.showER" :model="dataGenerator.generateModel(scope.row)" /></div>
+              <div class="model-ER-layout"><ModelERDiagram v-if="scope.row.showER" ref="erDiagram" source="modelList"  :show-shortcuts-group="false" :show-change-alert="false" :model="dataGenerator.generateModel(scope.row)" /></div>
               </el-popover>
               <span class="model-ER" v-popover="`${scope.row.alias}-ERPopover`">
                 <el-icon name="el-ksd-icon-table_er_diagram_22" class="ksd-fs-22" type="mult"></el-icon>
@@ -305,6 +251,7 @@ import AggregateModal from './AggregateModal/index.vue'
 import TableIndexEdit from '../TableIndexEdit/tableindex_edit'
 import ModelActions from './ModelActions/modelActions'
 import ModelERDiagram from '../../../common/ModelERDiagram/ModelERDiagram'
+import ModelTitleDescription from './Components/ModelTitleDescription'
 
 function getDefaultFilters (that) {
   return {
@@ -421,7 +368,8 @@ function getDefaultFilters (that) {
     TableIndexEdit,
     ModelOverview,
     ModelActions,
-    ModelERDiagram
+    ModelERDiagram,
+    ModelTitleDescription
   },
   locales
 })
@@ -927,6 +875,9 @@ export default class ModelList extends Vue {
   afterPoppoverEnter (e, row) {
     this.$nextTick(() => {
       this.$set(row, 'showER', true)
+      this.$nextTick(() => {
+        this.$refs.erDiagram && this.$refs.erDiagram.exchangePosition()
+      })
     })
   }
 }
@@ -1106,11 +1057,6 @@ export default class ModelList extends Vue {
     }
     .model-alias-title {
       max-width: calc(~'100% - 30px');
-      display: inline-block;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-      white-space: nowrap\0 !important;
     }
     .model-alias-item {
       .action-items {
@@ -1216,14 +1162,6 @@ export default class ModelList extends Vue {
       }
     }
   }
-  .alias {
-    font-weight: bold;
-    line-height: 20px;
-    width: 100%;
-    height: 20px;
-    // margin-bottom: 5px;
-    float: left;
-  }
   .last-modified {
     font-size: 12px;
     line-height: 18px;
@@ -1265,6 +1203,22 @@ export default class ModelList extends Vue {
     }
   }
 }
+.model_list_table{
+  .model-alias-label {
+    cursor: pointer;
+    .alias {
+      height: 20px;
+      margin-top: 0;
+      cursor: pointer;
+      .filter-status {
+        cursor: pointer;
+      }
+    }
+    .last-modified-tooltip {
+      cursor: pointer;
+    }
+  }
+}
 .filter-button {
   margin-left: 5px;
   vertical-align: bottom;
@@ -1315,69 +1269,15 @@ export default class ModelList extends Vue {
     }
   }
 }
-.last-modified-tooltip {
-  min-width: unset;
-  transform: translate(-5px, 5px);
-  margin-left: 15px;
-  color: #8B99AE;
-  font-size: 12px;
-  .popper__arrow {
-    left: 5px !important;
-  }
-}
-.status-tooltip {
-  min-width: unset;
-  transform: translate(-3px, 0);
-  .popper__arrow {
-    left: 8px !important;
-  }
-}
 .model-actions-dropdown {
   text-align: left;
   min-width: 95px;
 }
-.title-popover-layout {
-  font-size: 14px;
-  word-break: break-all;
-  .title {
-    color: @text-title-color;
-    font-weight: bold;
-  }
-  .label {
-    display: inline-block;
-    .group {
-      color: @text-disabled-color;
-      // text-align: right;
-      margin-right: 15px;
-      .title {
-        color: @text-disabled-color;
-        display: inline-block;
-        min-width: 40px;
-        word-break: break-all;
-        text-align: right;
-        margin-right: 10px;
-      }
-      .item {
-        display: inline-block;
-        max-width: 180px;
-        word-break: break-all;
-        vertical-align: top;
-      }
-    }
-    &.en {
-      .title {
-        min-width: 80px;
-      }
-      .item {
-        max-width: 140px;
-      }
-    }
-  }
-}
 .er-popover-layout {
   width: 400px;
   height: 300px;
   position: relative;
+  background-color: @ke-background-color-secondary;
   .model-ER-layout {
     width: 100%;
     height: 100%;
diff --git a/kystudio/src/components/studio/StudioModel/ModelList/locales.js b/kystudio/src/components/studio/StudioModel/ModelList/locales.js
index 6381a14a60..a05c38a8df 100644
--- a/kystudio/src/components/studio/StudioModel/ModelList/locales.js
+++ b/kystudio/src/components/studio/StudioModel/ModelList/locales.js
@@ -1,7 +1,6 @@
 export default {
   'en': {
     capbility: 'Favorite Rate',
-    dataLoadTime: 'Last Updated Time: ',
     status: 'Status',
     streamingTips: '流数据模型,部分功能不可用',
     modelPartitionSet: 'Model Partition',
@@ -41,7 +40,6 @@ export default {
     exitFullScreen: 'Exit Full Screen',
     usageTip: 'Times of the {mode} used by queries',
     model: 'model',
-    description: 'Description',
     indexGroup: 'index group',
     expansionRate: 'Expansion Rate',
     expansionRateTip: 'Expansion Rate = Storage Size / Source Table Size<br/>Note: The expansion rate won\'t show if the storage size is less than 1 GB.',
@@ -56,7 +54,6 @@ export default {
     OFFLINE: 'OFFLINE',
     BROKEN: 'BROKEN',
     status_c: 'Status: ',
-    modelStatus_c: 'Model Status:',
     modelType_c: 'Model Attributes: ',
     others: 'Others',
     reset: 'Reset',
@@ -68,9 +65,6 @@ export default {
     recommendations_c: 'Recommendation: ',
     clickToView: 'Review',
     filterModelOrOwner: 'Search model name',
-    modelSegmentHoleTips: 'This model\'s segment range has gaps in between. Empty results might be returned when querying those ranges. ',
-    modelSegmentHoleTips1: 'This model\'s batch segment range has gaps in between. Empty results might be returned when querying those ranges. ',
-    modelSegmentHoleTips2: 'This model\'s streaming data has gaps in between. Please contact technical support.',
     autoFix: 'automatic fix',
     ID: 'ID',
     column: 'Column',
@@ -84,7 +78,6 @@ export default {
     exportMetadataFailed: 'Can\'t export models at the moment. Please try again.',
     bokenModelExportMetadatasTip: 'Can\'t export model file at the moment as the model is BROKEN',
     importModels: 'Import Model',
-    emptyIndexTips: 'This model has unbuilt indexes. Please click the build index button to build the indexes.',
     guideToAcceptRecom: 'You may click on the recommendations besides the model name to view how to optimize.',
     overview: 'Overview',
     changeModelOwner: 'Change Owner',
@@ -99,17 +92,10 @@ export default {
     changeDesc: 'You can change the owner of the model to a system admin, a user in the project ADMIN role, or a user in the project management role.',
     buildIndex: 'Build Index',
     batchBuildSubTitle: 'Please choose which data ranges you\'d like to build with the added indexes.',
-    modelMetadataChangedTips: 'Data in the source table was changed. The query results might be inaccurate. ',
-    seeDetail: 'View Details',
-    // modelMetadataChangedDesc: 'Source table in the following segment(s) might have been changed. The data might be inconsistent after being built. Please check with your system admin.<br/>You may try refreshing these segments to ensure the data consistency.',
-    noSegmentOnlineTip: 'This model can\'t go online as it doesn\'t have segments. Models with no segment couldn\'t serve queries. Please add a segment.',
     refrashWarningSegment: 'Only ONLINE segments could be refreshed',
     closeSCD2ModalOnlineTip: 'This model can\'t go online as it includes non-equal join conditions(≥, <). Please delete those join conditions, or turn on `Support History table` in project settings.',
-    SCD2ModalOfflineTip: 'This model includes non-equal join conditions (≥, <), which are not supported at the moment. Please delete those join conditions, or turn on `Support History table` in project settings.',
     storageTip: 'Calculates the amount of data built in this model',
     subPartitionValuesManage: 'Manage Sub-Partition Values',
-    multilParTip: 'This model used multilevel partitioning, which are not supported at the moment. Please set subpartition as \'None\' in model partition dialog, or turn on \'Multilevel Partitioning\' in project settings.',
-    streaming: 'Streaming',
     segmentHoletips: 'There exists a gap in the segment range, and the data of this range cannot be queried. Please confirm whether to add the following segments to fix.',
     fixSegmentTitle: 'Fix Segment'
   }
diff --git a/kystudio/src/components/studio/StudioModel/TableIndexEdit/tableindex_edit.vue b/kystudio/src/components/studio/StudioModel/TableIndexEdit/tableindex_edit.vue
index 08340e79a7..c34a90dcc0 100644
--- a/kystudio/src/components/studio/StudioModel/TableIndexEdit/tableindex_edit.vue
+++ b/kystudio/src/components/studio/StudioModel/TableIndexEdit/tableindex_edit.vue
@@ -1,12 +1,6 @@
 <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()">
-      <!-- <el-form :model="tableIndexMeta" :rules="rules" ref="tableIndexForm" label-position="top">
-        <el-form-item :label="$t('tableIndexName')" prop="name">
-          <el-input v-focus="isShow" v-model="tableIndexMeta.name" auto-complete="off" placeholder="" size="medium" style="width:500px"></el-input>
-        </el-form-item>
-      </el-form> -->
-      <!-- <div class="ky-line ksd-mtb-10"></div> -->
       <div class="table-index-list">
         <div class="ksd-mb-10" v-if="modelInstance.model_type === 'HYBRID'">
           <h4>
@@ -24,7 +18,6 @@
             </el-tooltip>
           </el-radio-group>
         </div>
-        <!-- <el-button type="primary" plain size="medium" @click="selectAll">{{$t('selectAllColumns')}}</el-button><el-button plain size="medium" @click="clearAll">{{$t('clearAll')}}</el-button> -->
         <div class="header">
           <h4 class="ksd-left" v-if="modelInstance.model_type === 'HYBRID'">{{$t('includeColumns')}}</h4>
           <el-alert
@@ -36,10 +29,6 @@
           </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-tooltip effect="dark" placement="top">
-              <div slot="content" v-html="$t('excludeTableCheckboxTip')"></div>
-              <el-checkbox class="ksd-mr-5" v-if="showExcludedTableCheckBox" v-model="displayExcludedTables">{{$t('excludeTableCheckbox')}}</el-checkbox>
-            </el-tooltip> -->
             <el-input v-model="searchColumn" size="medium" prefix-icon="el-ksd-icon-search_22" style="width:200px" :placeholder="$t('filterByColumns')"></el-input>
           </template>
         </div>
@@ -85,10 +74,9 @@
        </div>
       </div>
       <div slot="footer" class="dialog-footer ky-no-br-space">
-        <!-- <el-checkbox v-model="tableIndexMeta.load_data" :label="true" class="ksd-fleft ksd-mt-8">{{$t('catchup')}}</el-checkbox> -->
         <el-button :type="onlyBatchType ? 'primary' : ''" :text="onlyBatchType" @click="closeModal" size="medium">{{$t('kylinLang.common.cancel')}}</el-button>
-        <el-button :type="!onlyBatchType ? 'primary' : ''" :loading="btnLoading" size="medium" @click="submit(false)" :disabled="saveBtnDisable">{{$t('kylinLang.common.save')}}</el-button>
-        <el-button v-if="onlyBatchType" type="primary" :loading="btnLoading" size="medium" @click="submit(true)" :disabled="saveBtnDisable">{{$t('saveAndBuild')}}</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>
@@ -159,6 +147,7 @@
     cloneMeta = ''
     isSelectAllTableIndex = false
     displayExcludedTables = false
+    isLoadDataLoading = false
 
     @Watch('searchColumn')
     changeSearchColumn (val) {
@@ -174,9 +163,6 @@
       return (this.modelInstance.model_type === 'HYBRID' && this.tableIndexMeta.index_range !== 'STREAMING') || (this.modelInstance.model_type !== 'STREAMING' && this.modelInstance.model_type !== 'HYBRID')
     }
 
-    // get showExcludedTableCheckBox () {
-    //   return this.allColumns.length ? this.allColumns.filter(it => typeof it.excluded !== 'undefined' && it.excluded).length > 0 : false
-    // }
     topRow (col) {
       let index = this.getRowIndex(col, 'fullName')
       this.allColumns.splice(0, 0, col)
@@ -248,14 +234,9 @@
     }
     getAllColumns () {
       this.allColumns = []
-      // let result = []
       let result = this.modelInstance.selected_columns.map((c) => {
         return { fullName: c.column, cardinality: c.cardinality, excluded: typeof c.excluded !== 'undefined' ? c.excluded : true }
       })
-      // let modelUsedTables = this.modelInstance && this.modelInstance.getTableColumns() || []
-      // modelUsedTables.forEach((col) => {
-      //   result.push(col.full_colname)
-      // })
       if (this.tableIndexMeta.col_order.length) {
         const selected = this.tableIndexMeta.col_order.map(item => {
           const index = result.findIndex(it => it.fullName === item)
@@ -264,11 +245,6 @@
         const unSort = result.filter(item => !this.tableIndexMeta.col_order.includes(item.fullName))
         result = [...selected, ...unSort]
       }
-      // cc列也要放到这里
-      // let ccColumns = this.modelInstance && this.modelInstance.computed_columns || []
-      // ccColumns.forEach((col) => {
-      //   result.push(col.tableAlias + '.' + col.columnName)
-      // })
       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) {
@@ -331,10 +307,8 @@
     closeModal (isSubmit) {
       this.hideModal()
       this.btnLoading = false
-      // this.tableIndexMeta.name = ''
       this.searchColumn = ''
       this.isSelectAllTableIndex = false
-      // this.$refs.tableIndexForm.resetFields()
       setTimeout(() => {
         this.callback && this.callback({
           isSubmit: isSubmit
@@ -376,6 +350,7 @@
       }
     }
     confirmSubmit (isLoadData) {
+      this.isLoadDataLoading = isLoadData
       this.btnLoading = true
       let successCb = (res) => {
         handleSuccess(res, (data) => {
@@ -399,7 +374,6 @@
       }
       // 按照sort选中列的顺序对col_order进行重新排序
       this.tableIndexMeta.col_order = []
-      // this.tableIndexMeta.sort_by_columns = []
       this.tableIndexMeta.shard_by_columns = []
       this.allColumns.forEach((col) => {
         if (col.isUsed) {
@@ -408,9 +382,6 @@
         if (col.isShared) {
           this.tableIndexMeta.shard_by_columns.push(col.fullName)
         }
-        // if (col.isSorted) {
-        //   this.tableIndexMeta.sort_by_columns.push(col.fullName)
-        // }
       })
       this.tableIndexMeta.project = this.currentSelectedProject
       this.tableIndexMeta.model_id = this.isHybridBatch ? this.modelInstance.batch_id : this.modelInstance.uuid
@@ -442,13 +413,11 @@
       this.allColumns.forEach(item => {
         if (v && this.getDisabledTableType(item)) return
         item.isUsed = v
-        // item.isSorted = v
       })
     }
     selectTableIndex (status, col) {
       const selectedColumns = this.getSelectedColumns
       const unSelected = this.allColumns.filter(it => !it.isUsed)
-      // col.isSorted = status
       this.allColumns = [...selectedColumns, ...unSelected]
       selectedColumns.length === this.allColumns.length && (this.isSelectAllTableIndex = true)
       unSelected.length === this.allColumns.length && (this.isSelectAllTableIndex = false)
diff --git a/kystudio/src/directive/index.js b/kystudio/src/directive/index.js
index 143772e3af..86b2c3cecb 100644
--- a/kystudio/src/directive/index.js
+++ b/kystudio/src/directive/index.js
@@ -657,9 +657,16 @@ Vue.directive('custom-tooltip', {
       textWidth: currentElWidth,
       binding,
       timer: null,
-      [`resizeFn-${id}`]: function () {
-        const { parent } = parentList[id]
+      [`resizeFn-${id}`]: function (e) {
+        const { parent } = parentList[id] ?? {}
         if (!parent) return
+        // 监听盒子 style 里 width 的变化
+        if (binding.value.observerId) {
+          const [mutationRecord] = e.slice(-1)
+          const oldValue = mutationRecord.oldValue.match(/(width: [\w.]+);/g)
+          const newValue = mutationRecord.target.style.cssText.match(/(width: [\w.]+);/g)
+          if (oldValue.join('') === newValue.join('')) return
+        }
         let textNode = parent.querySelector('.custom-tooltip-text')
         setTimeout(() => {
           // 此方法的调用需要等待 dom 被更新完成
@@ -702,13 +709,17 @@ Vue.directive('custom-tooltip', {
 
 // MutationObserver 方式监听 dom attrs 的改变
 function licenseDom (id) {
-  if (!parentList[id]) {
-    return
-  }
+  if (!parentList[id]) return
   const { binding } = parentList[id]
   // 当元素在table中,使用 MutationObserver 方法监听 table 宽度 style 的改变(这里监听的是 el-table__body dom 的宽度)
-  if (binding.value.tableClassName) {
-    let element = document.querySelector(`.${binding.value.tableClassName}`) && document.querySelector(`.${binding.value.tableClassName}`).querySelector('.el-table__body')
+  const observerElementName = binding.value.tableClassName || binding.value.observerId
+  if (observerElementName) {
+    let element = ''
+    if (binding.value.tableClassName) {
+      element = document.querySelector(`.${observerElementName}`) && document.querySelector(`.${observerElementName}`).querySelector('.el-table__body')
+    } else {
+      element = document.getElementById(`${observerElementName}`)
+    }
     let MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver
     if (parentList[id]) {
       let observer = new MutationObserver(
@@ -717,7 +728,8 @@ function licenseDom (id) {
       parentList[id].observer = observer
       observer.observe(element, {
         attributes: true, // 监听 table 的 attributes 的改变
-        attributeFilter: ['drag-count']
+        attributeFilter: ['drag-count', 'style'],
+        attributeOldValue: true
       })
     }
   } else {
diff --git a/kystudio/src/router/index.js b/kystudio/src/router/index.js
index 10c8133b78..b0b6b3c545 100644
--- a/kystudio/src/router/index.js
+++ b/kystudio/src/router/index.js
@@ -71,7 +71,7 @@ let routerOptions = {
         name: 'ModelEdit',
         path: 'studio/model/:modelName/:action',
         meta: {},
-        component: () => import('../components/studio/StudioModel/ModelTabs/index.vue')
+        component: () => import('../components/studio/StudioModel/ModelEdit/index.vue')
       }, {
         name: 'ModelSubPartitionValues',
         path: 'studio/model_sub_partition_values/:modelName/:modelId',
diff --git a/kystudio/src/service/model.js b/kystudio/src/service/model.js
index 33d32f9f28..a563b3de6b 100644
--- a/kystudio/src/service/model.js
+++ b/kystudio/src/service/model.js
@@ -326,5 +326,8 @@ export default {
   },
   loadSecondStorageScanRows (para) {
     return Vue.resource(apiUrl + 'query/query_history/tired_storage_metrics').get(para)
+  },
+  validateExportTds: (para) => {
+    return Vue.resource(apiUrl + 'models/validate_export?project=' + para.project + '&model=' + para.modelId).get()
   }
 }
diff --git a/kystudio/src/service/monitor.js b/kystudio/src/service/monitor.js
index 673195fa77..65959fde3c 100644
--- a/kystudio/src/service/monitor.js
+++ b/kystudio/src/service/monitor.js
@@ -11,12 +11,6 @@ export default {
   getJobDetail: (para) => {
     return Vue.resource(apiUrl + 'jobs/' + para.job_id + '/detail').get(para)
   },
-  losdWaittingJobModels: (para) => {
-    return Vue.resource(apiUrl + 'jobs/waiting_jobs/models').get(para)
-  },
-  laodWaittingJobsByModel: (para) => {
-    return Vue.resource(apiUrl + 'jobs/waiting_jobs').get(para)
-  },
   getSlowQueries: (para) => {
     return Vue.resource(apiUrl + 'diag/slow_query').get(para.page)
   },
diff --git a/kystudio/src/store/model.js b/kystudio/src/store/model.js
index cbcf4e1615..f4a8219bb4 100644
--- a/kystudio/src/store/model.js
+++ b/kystudio/src/store/model.js
@@ -407,6 +407,9 @@ export default {
     },
     [types.CHECK_INTERNAL_MEASURE] (_, params) {
       return api.model.checkInternalMeasure(params)
+    },
+    [types.VALIDATE_EXPORT_TDS] (_, params) {
+      return api.model.validateExportTds(params)
     }
   },
   getters: {
diff --git a/kystudio/src/store/monitor.js b/kystudio/src/store/monitor.js
index 75c830a7ea..ff5568a785 100644
--- a/kystudio/src/store/monitor.js
+++ b/kystudio/src/store/monitor.js
@@ -11,9 +11,6 @@ export default {
     [types.GET_JOB_DETAIL]: function ({ commit }, para) {
       return api.monitor.getJobDetail(para)
     },
-    [types.LOAD_WAITTING_JOBS_BY_MODEL]: function ({ commit }, para) {
-      return api.monitor.laodWaittingJobsByModel(para)
-    },
     [types.EXPORT_PUSHDOWN]: function ({ commit }, para) {
       return api.monitor.exportPushDownQueries(para)
     },
diff --git a/kystudio/src/store/types.js b/kystudio/src/store/types.js
index c829a94feb..29a4f333de 100644
--- a/kystudio/src/store/types.js
+++ b/kystudio/src/store/types.js
@@ -285,6 +285,7 @@ export const GET_JOB_SIMPLE_LOG = 'GET_JOB_SIMPLE_LOG'
 export const VALIDATE_DATE_FORMAT = 'VALIDATE_DATE_FORMAT'
 export const CHECK_INTERNAL_MEASURE = 'CHECK_INTERNAL_MEASURE'
 export const UPDATE_FILTER_MODEL_NAME_CLOUD = 'UPDATE_FILTER_MODEL_NAME_CLOUD'
+export const VALIDATE_EXPORT_TDS = 'VALIDATE_EXPORT_TDS'
 // table index
 export const GET_TABLE_INDEX = 'GET_TABLE_INDEX'
 export const EDIT_TABLE_INDEX = 'EDIT_TABLE_INDEX'
@@ -367,7 +368,6 @@ export const VALIDATE_MODEL_NAME = 'VALIDATE_MODEL_NAME'
 export const LOAD_JOBS_LIST = 'LOAD_JOBS_LIST'
 export const GET_JOB_DETAIL = 'GET_JOB_DETAIL'
 export const SAVE_JOBS_LIST = 'SAVE_JOBS_LIST'
-export const LOAD_WAITTING_JOBS_BY_MODEL = 'LOAD_WAITTING_JOBS_BY_MODEL'
 export const LOAD_SLOW_QUERIES = 'LOAD_SLOW_QUERIES'
 export const SAVE_SLOW_QUERIES = 'SAVE_SLOW_QUERIES'
 export const LOAD_STEP_OUTPUTS = 'LOAD_STEP_OUTPUTS'
diff --git a/kystudio/src/util/dataGenerator.js b/kystudio/src/util/dataGenerator.js
index f8e583f8bc..d25206b7b9 100644
--- a/kystudio/src/util/dataGenerator.js
+++ b/kystudio/src/util/dataGenerator.js
@@ -38,7 +38,7 @@ function getTablesData (modelData) {
 
   try {
     // lookups和join_tables任选其一,组合成维度表集合
-    const lookupsInfo = modelData.lookups || modelData.join_tables || []
+    const lookupsInfo = modelData.join_tables || []
     const factAlias = modelData.fact_table.split('.')[1]
     const factInfo = { table: modelData.fact_table, alias: factAlias, kind: 'FACT' }
     const tableColumnDataMap = modelData.simplified_tables.reduce((map, tableData) => ({
@@ -82,7 +82,7 @@ function formatJoinsData (jointData, tablesData) {
   let joinsData = []
 
   try {
-    const { primary_key: pKeys, foreign_key: fKeys } = jointData
+    const { primary_key: pKeys, foreign_key: fKeys, simplified_non_equi_join_conditions } = jointData
     joinsData = pKeys.map((pKey, index) => {
       const [tb] = tablesData.filter(it => it.alias === fKeys[index].split('.')[0])
       return {
@@ -91,6 +91,17 @@ function formatJoinsData (jointData, tablesData) {
         foreignKey: fKeys[index]
       }
     })
+    // no equal joins
+    if (simplified_non_equi_join_conditions) {
+      simplified_non_equi_join_conditions.forEach(item => {
+        const [tb] = tablesData.filter(it => it.alias === item.foreign_key.split('.')[0])
+        joinsData.push({
+          guid: tb ? tb.guid : sampleGuid(),
+          primaryKey: item.primary_key,
+          foreignKey: item.foreign_key
+        })
+      })
+    }
     // 模型数据结构不严谨,防错用
     if (pKeys.length !== fKeys.length) {
       throw new Error('The length of primary keys isn\'t the same as foreign keys.')
diff --git a/kystudio/src/util/domHelper.js b/kystudio/src/util/domHelper.js
index 41a04bc82a..ddec46a24e 100644
--- a/kystudio/src/util/domHelper.js
+++ b/kystudio/src/util/domHelper.js
@@ -40,9 +40,12 @@ export const download = {
   }
 }
 
-export function createToolTipDom (el, options = {}) {
+export function createToolTipDom (el, options = {}, className) {
   const customLayout = document.createElement('span')
   const renderer = Vue.compile(el)
+
+  className && (customLayout.className += ` ${className}`)
+  
   let createCommonTip = (propsData) => {
     let Dom = Vue.extend(ElementUI.Tooltip)
     // let Dom = Vue.extend(commonTip)
diff --git a/kystudio/src/util/plumb.js b/kystudio/src/util/plumb.js
index d17665bfd0..2e7bf583d6 100644
--- a/kystudio/src/util/plumb.js
+++ b/kystudio/src/util/plumb.js
@@ -28,8 +28,8 @@ export function jsPlumbTool () {
       },
       isSource: true,
       isTarget: true,
-      // connector: [ 'Flowchart', { stub: 1, gap: 1, cornerRadius: 3, alwaysRespectStubs: true } ], // 设置连线为直角曲线
-      connector: [ 'Bezier', { curviness: 22 } ],
+      connector: [ 'Flowchart', { stub: 1, gap: 1, cornerRadius: 3, alwaysRespectStubs: true } ], // 设置连线为直角曲线
+      // connector: [ 'Bezier', { curviness: 22 } ],
       connectorStyle: {
         strokeWidth: strokeWidth,
         stroke: lineColor
@@ -92,6 +92,7 @@ export function jsPlumbTool () {
       this.endpointConfig.hoverPaintStyle.fill = hoverBrokenColor
       this.endpointConfig.connectorStyle.stroke = brokenColor
       this.endpointConfig.connectorHoverStyle.stroke = hoverBrokenColor
+      this.endpointConfig.connectorStyle['cssClass'] = 'broken-connector'
     },
     lazyRender (cb) {
       jsPlumb.setSuspendDrawing(true)
@@ -135,24 +136,6 @@ export function jsPlumbTool () {
         DragOptions: { cursor: 'pointer', zIndex: 2000 },
         HoverPaintStyle: this.endpointConfig.connectorStyle,
         ConnectionOverlays: [
-          // [ 'Arrow', {
-          //   location: 1,
-          //   visible: true,
-          //   width: 11,
-          //   length: 11,
-          //   id: 'ARROW',
-          //   foldback: 2
-          // } ],
-          // [ 'Custom', {
-          //   location: 1,
-          //   create: (component) => {
-          //     const dom = document.createElement('span')
-          //     dom.innerHTML = '<span class="line-end-pointer">&bull;</span>'
-          //     return dom
-          //   },
-          //   id: 'customPoint',
-          //   cssClass: 'line-end'
-          // }]
         ],
         Container: el
       })
@@ -210,7 +193,7 @@ export function jsPlumbTool () {
             id: pid + (fid + 'label'),
             events: {
               mousedown: function (_, e) {
-                stopPropagation(e)
+                otherProper.cancelBubble && stopPropagation(e)
                 return false
               },
               click: function (_, e) {