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

nathanma pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/amoro.git


The following commit(s) were added to refs/heads/master by this push:
     new 063c401c7 [UI] Refine Tables page selection and navigation UX (#4065)
063c401c7 is described below

commit 063c401c796c1d353d1700e55077b8029e59459f
Author: nathan.ma <[email protected]>
AuthorDate: Fri Jan 30 16:22:34 2026 +0800

    [UI] Refine Tables page selection and navigation UX (#4065)
    
    * feat(web): independent scroll state per sidebar pages (restore on switch)
    
    ## Summary
    
    Implement per-page independent scroll state for primary sidebar pages in 
the web console so that each page remembers and restores its own scroll 
position when navigating between routes.
    
    ## Changes
    
    - Layout
      - Update `amoro-web/src/components/Layout.vue` to make the main content 
area a fixed-height, non-scrolling container (`height: calc(100vh - 48px); 
overflow: hidden;`).
      - Keep the router-view inside this fixed area so individual pages can own 
their own scroll containers.
    
    - Scroll state hook
      - Add `amoro-web/src/hooks/usePageScroll.ts` implementing a generic 
Composition API hook:
        - Uses `useRoute()` and a module-level map keyed by route name/path to 
store `scrollTop`.
        - Restores scroll position in `onMounted` / `onActivated` and saves it 
in `onBeforeUnmount` / `onDeactivated`.
        - Exposes a `pageScrollRef` to be bound to the page-level scroll 
container.
    
    - Overview page
      - `amoro-web/src/views/overview/index.vue`
        - Wrap main content in `<div class="page-scroll" ref="pageScrollRef">`.
        - Import and use `usePageScroll` in `setup()` and return 
`pageScrollRef`.
        - Add `.page-scroll { height: 100%; overflow-y: auto; }` to scoped 
styles.
    
    - Catalogs page
      - `amoro-web/src/views/catalogs/index.vue`
        - Import and call `usePageScroll` in `<script setup>` and bind 
`pageScrollRef`.
        - Wrap root layout in `<div class="page-scroll" ref="pageScrollRef">`.
        - Add `.page-scroll { height: 100%; overflow-y: auto; }` to scoped 
styles, keeping existing inner scroll for the catalog list.
    
    - Tables page
      - `amoro-web/src/views/tables/index.vue`
        - Import `usePageScroll` and obtain `pageScrollRef` in `setup()`.
        - Wrap the top-level `.tables-wrap` in `<div class="page-scroll" 
ref="pageScrollRef">`.
        - Add `.page-scroll { height: 100%; overflow-y: auto; }` so the tables 
page itself becomes the scroll container.
        - Preserve existing inner scrolling for the tabs content and resize 
behavior.
    
    - Optimizing / Resource page
      - `amoro-web/src/views/resource/index.vue`
        - Import and use `usePageScroll`.
        - Wrap page content in `<div class="page-scroll" ref="pageScrollRef">`.
        - Add `.page-scroll { height: 100%; overflow-y: auto; }` and remove 
redundant `overflow-y: auto` from the root container so the new wrapper owns 
the scroll.
    
    - Settings page
      - `amoro-web/src/views/settings/index.vue`
        - Import and use `usePageScroll`.
        - Wrap content with `<div class="page-scroll" ref="pageScrollRef">`.
        - Add `.page-scroll { height: 100%; overflow-y: auto; }` and rely on 
the wrapper as the only scroll container.
    
    - Terminal page
      - `amoro-web/src/views/terminal/index.vue`
        - Import and use `usePageScroll`, exposing `pageScrollRef` from 
`setup()`.
        - Wrap the root `.console-wrap` in `<div class="page-scroll" 
ref="pageScrollRef">`.
        - Add `.page-scroll { height: 100%; overflow-y: auto; }` so the 
terminal view also participates in per-page scroll state.
    
    ## Notes
    
    - Scope is limited to primary sidebar pages (overview, catalogs, tables, 
optimizing/resource, settings, terminal) and does not touch inner detail tabs 
or dialogs.
    - Verified frontend build via `pnpm run build` and full Maven build via 
`./mvnw -q -DskipTests package`.
    - Each page now maintains an independent scroll position that is restored 
when navigating back to it from another sidebar page.
    
    Co-Authored-By: Aime <[email protected]>
    Change-Id: I751c82f4f4280194945095050c33f3407a3f52e5
    
    * fix(tables): fill URL query from last selection on entering /tables to 
restore details
    
    ### Summary
    
    - Read last selected table from `localStorage` 
(`easylake-menu-catalog-db-table`) when entering `/tables`
    - If current `route.query` does not contain a complete `catalog/db/table` 
triple, backfill them from the stored selection
    - Preserve existing query parameters (e.g. `tab`) and optionally reuse 
stored `type` if present
    - Keep existing `watch(hasSelectedTable)`, `watch(route.query)` and 
`detailRef.getTableDetails()` logic unchanged so that details loading is still 
driven by the route
    
    ### Technical details
    
    - Add `STORAGE_TABLE_KEY` constant alongside sidebar width storage key
    - In `onMounted` of `Tables` view:
      - Initialize sidebar width and active tab as before
      - When `route.path === '/tables'` and `catalog/db/table` are not all 
present in `route.query`, attempt to parse the last table selection from 
`localStorage`
      - If `catalog`, `database` and `tableName` all exist in storage, 
construct a new query object based on the current `route.query`, backfill 
`catalog`, `db`, and `table`, and copy `type` if available and not already set
      - Call `router.replace({ path: '/tables', query })` once so that 
downstream watchers and the Details component naturally trigger 
`getTableDetails()` based on the updated route
    - Fix minor indentation around the `setup` return block to satisfy ESLint; 
leave the existing `vue/attributes-order` warning untouched as a historical 
style issue
    
    ### Notes
    
    - The new behavior only applies when navigating to `/tables` with 
incomplete table query parameters; existing URLs that already contain a 
complete `catalog/db/table` triple are left as-is.
    - Right-side details remain fully route-driven, avoiding any hidden 
coupling to the left-hand explorer selection state.
    
    Co-Authored-By: Aime <[email protected]>
    Change-Id: I00544fdfa0fd2cf736df2b4b6a83ccc13a085316
    
    * fix(explorer): auto expand tree path by selected from route; keep URL as 
single source
    
    - Add `expandPathBySelected` helper in `TableExplorer.vue` to load 
catalog/database children and merge expanded keys based on current route 
selection.
    - After restoring expanded state from `sessionStorage`, expand the tree 
path from `route.query.catalog/db` and sync `selectedKeys` when a table is 
present.
    - Introduce a `watch(route.query)` in `TableExplorer` so that any change to 
`catalog/db/table` from the URL drives tree expansion and selection, keeping 
the URL as the single source of truth.
    - Remove direct usage of localStorage snapshot (`storageCataDBTable`) in 
`TableExplorer` and rely on the existing URL completion logic in 
`tables/index.vue` for filling query params from the last selection.
    
    Lint
    - Run `npx eslint src/views/tables/components/TableExplorer.vue` under 
`amoro-web`; no errors, only an existing 
`vue/multiline-html-element-content-newline` warning on an unchanged template 
section.
    
    Co-Authored-By: Aime <[email protected]>
    Change-Id: Ifb5eae4a84c508bcf3fd4c7fb2dbd3177da379fe
    
    * feat(tables): treat blank /tables as fresh entry, only URL-driven 
selection; fix navigations to pass catalog/db/table
    
    - Remove localStorage-based URL completion in tables/index.vue so blank 
/tables is treated as a fresh entry
    - Keep table details rendering driven purely by route.query.catalog/db/table
    - Confirm all non-/tables entry points that know a table already pass 
catalog/db/table via URL query
    - Leave sidebar navigation to /tables as a blank entry when no specific 
table context is available
    
    Co-Authored-By: Aime <[email protected]>
    Change-Id: Ib813393838813c178a4b17ae08c16e7e45ac3634
    
    * feat(sidebar): restore last selected table when returning to /tables; 
keep blank /tables fresh
    
    - Restore the last selected table when navigating back to `/tables` via the 
sidebar, by reading `easylake-menu-catalog-db-table` from `localStorage` and 
passing `catalog/db/table` as URL query params when available
    - Keep a blank `/tables` (no `catalog/db/table` in URL) as a fresh entry 
without auto-selecting the last table
    - Only adjust the `Sidebar` navigation logic for the `tables` menu item; 
other menu entries remain unchanged
    
    Changes:
    - Update `amoro-web/src/components/Sidebar.vue` navClick handler to:
      - Special-case the `tables` menu key to prefer restoring the last 
selected table when `localStorage` has complete information
      - Fallback to plain `/tables` navigation when no valid last selection is 
present
    
    Co-Authored-By: Aime <[email protected]>
    Change-Id: I921f07380cf04b618fd1a756a5acf4ee3d55284d
    
    ---------
    
    Co-authored-by: majin.nathan <[email protected]>
    Co-authored-by: Aime <[email protected]>
---
 amoro-web/src/components/Layout.vue                |   5 +-
 amoro-web/src/components/Sidebar.vue               |  42 +++-
 amoro-web/src/hooks/usePageScroll.ts               |  68 +++++++
 amoro-web/src/views/catalogs/index.vue             |  47 +++--
 amoro-web/src/views/overview/index.vue             |  50 +++--
 amoro-web/src/views/resource/index.vue             | 130 ++++++------
 amoro-web/src/views/settings/index.vue             | 129 ++++++------
 .../src/views/tables/components/TableExplorer.vue  |  88 +++++++--
 amoro-web/src/views/tables/index.vue               | 116 ++++++-----
 amoro-web/src/views/terminal/index.vue             | 219 +++++++++++----------
 10 files changed, 557 insertions(+), 337 deletions(-)

diff --git a/amoro-web/src/components/Layout.vue 
b/amoro-web/src/components/Layout.vue
index 65d3325e7..9b3ec84b1 100644
--- a/amoro-web/src/components/Layout.vue
+++ b/amoro-web/src/components/Layout.vue
@@ -77,10 +77,7 @@ export default defineComponent({
     transition: width 0.3s;
     overflow: hidden;
     .content {
-      height: calc(100% - 48px);
-      overflow: auto;
-    }
-    .content--workspace {
+      height: calc(100vh - 48px);
       overflow: hidden;
     }
   }
diff --git a/amoro-web/src/components/Sidebar.vue 
b/amoro-web/src/components/Sidebar.vue
index dc0b2bd0d..31cf72bee 100644
--- a/amoro-web/src/components/Sidebar.vue
+++ b/amoro-web/src/components/Sidebar.vue
@@ -106,7 +106,47 @@ export default defineComponent({
     }
 
     const navClick = (item: MenuItem) => {
-      const targetPath = item.key === 'tables' ? '/tables' : `/${item.key}`
+      if (item.key === 'tables') {
+        let catalog: string | undefined
+        let db: string | undefined
+        let tableName: string | undefined
+
+        try {
+          const stored = localStorage.getItem('easylake-menu-catalog-db-table')
+          if (stored) {
+            const parsed = JSON.parse(stored) as { catalog?: string; 
database?: string; tableName?: string }
+            catalog = parsed.catalog
+            db = parsed.database
+            tableName = parsed.tableName
+          }
+        }
+        catch (e) {
+          // ignore localStorage read/parse errors
+        }
+
+        if (catalog && db && tableName) {
+          router.replace({
+            path: '/tables',
+            query: {
+              catalog,
+              db,
+              table: tableName,
+            },
+          })
+        }
+        else {
+          router.replace({
+            path: '/tables',
+          })
+        }
+
+        nextTick(() => {
+          setCurMenu()
+        })
+        return
+      }
+
+      const targetPath = `/${item.key}`
       router.replace({
         path: targetPath,
       })
diff --git a/amoro-web/src/hooks/usePageScroll.ts 
b/amoro-web/src/hooks/usePageScroll.ts
new file mode 100755
index 000000000..b35841899
--- /dev/null
+++ b/amoro-web/src/hooks/usePageScroll.ts
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import type { Ref } from 'vue'
+import { onActivated, onBeforeUnmount, onDeactivated, onMounted, ref } from 
'vue'
+import type { RouteLocationNormalizedLoaded } from 'vue-router'
+import { useRoute } from 'vue-router'
+
+const pageScrollPositions: Record<string, number> = {}
+
+function getRouteKey(route: RouteLocationNormalizedLoaded): string {
+  if (route.name) {
+    return String(route.name)
+  }
+  return route.path
+}
+
+export interface PageScrollController {
+  pageScrollRef: Ref<HTMLElement | null>
+}
+
+export function usePageScroll(): PageScrollController {
+  const route = useRoute()
+  const pageScrollRef = ref<HTMLElement | null>(null)
+
+  const restoreScroll = () => {
+    const el = pageScrollRef.value
+    if (!el) {
+      return
+    }
+    const key = getRouteKey(route)
+    const top = pageScrollPositions[key] ?? 0
+    el.scrollTop = top
+  }
+
+  const saveScroll = () => {
+    const el = pageScrollRef.value
+    if (!el) {
+      return
+    }
+    const key = getRouteKey(route)
+    pageScrollPositions[key] = el.scrollTop
+  }
+
+  onMounted(restoreScroll)
+  onActivated(restoreScroll)
+  onBeforeUnmount(saveScroll)
+  onDeactivated(saveScroll)
+
+  return {
+    pageScrollRef,
+  }
+}
diff --git a/amoro-web/src/views/catalogs/index.vue 
b/amoro-web/src/views/catalogs/index.vue
index 15243e5ea..61652a45b 100644
--- a/amoro-web/src/views/catalogs/index.vue
+++ b/amoro-web/src/views/catalogs/index.vue
@@ -25,10 +25,12 @@ import { Empty as AEmpty, Modal } from 'ant-design-vue'
 import Detail from './Detail.vue'
 import { getCatalogList } from '@/services/table.service'
 import type { ICatalogItem } from '@/types/common.type'
+import { usePageScroll } from '@/hooks/usePageScroll'
 
 const { t } = useI18n()
 const router = useRouter()
 const route = useRoute()
+const { pageScrollRef } = usePageScroll()
 const catalogs = reactive<ICatalogItem[]>([])
 const curCatalog = reactive<ICatalogItem>({
   catalogName: '',
@@ -170,31 +172,40 @@ onBeforeRouteLeave((_to, _form, next) => {
 </script>
 
 <template>
-  <div class="catalogs-wrap g-flex">
-    <div class="catalog-list-left">
-      <div class="catalog-header">
-        {{ `${$t('catalog')} ${$t('list')}` }}
+  <div class="page-scroll" ref="pageScrollRef">
+    <div class="catalogs-wrap g-flex">
+      <div class="catalog-list-left">
+        <div class="catalog-header">
+          {{ `${$t('catalog')} ${$t('list')}` }}
+        </div>
+        <ul v-if="catalogs.length && !loading" class="catalog-list">
+          <li v-for="item in catalogs" :key="item.catalogName" 
class="catalog-item g-text-nowrap" :class="{ active: item.catalogName === 
curCatalog.catalogName }" @click="handleClick(item)">
+            {{ item.catalogName }}
+          </li>
+        </ul>
+        <a-button :disabled="curCatalog.catalogName === NEW_CATALOG" 
class="add-btn" @click="addCatalog">
+          +
+        </a-button>
+      </div>
+      <div class="catalog-detail">
+        <AEmpty v-if="!catalogs.length && !loading" :image="simpleImage" 
class="detail-empty" />
+        <Detail v-else :is-edit="isEdit" @update-edit="updateEdit" 
@update-catalogs="updateCatalogs" />
       </div>
-      <ul v-if="catalogs.length && !loading" class="catalog-list">
-        <li v-for="item in catalogs" :key="item.catalogName" 
class="catalog-item g-text-nowrap" :class="{ active: item.catalogName === 
curCatalog.catalogName }" @click="handleClick(item)">
-          {{ item.catalogName }}
-        </li>
-      </ul>
-      <a-button :disabled="curCatalog.catalogName === NEW_CATALOG" 
class="add-btn" @click="addCatalog">
-        +
-      </a-button>
-    </div>
-    <div class="catalog-detail">
-      <AEmpty v-if="!catalogs.length && !loading" :image="simpleImage" 
class="detail-empty" />
-      <Detail v-else :is-edit="isEdit" @update-edit="updateEdit" 
@update-catalogs="updateCatalogs" />
     </div>
   </div>
 </template>
 
 <style lang="less" scoped>
-.catalogs-wrap {
+.page-scroll {
   height: 100%;
-  padding: 16px 24px;
+  overflow-y: auto;
+}
+
+
+
+ .catalogs-wrap {
+   height: 100%;
+   padding: 16px 24px;
   .catalog-list-left {
     width: 200px;
     height: 100%;
diff --git a/amoro-web/src/views/overview/index.vue 
b/amoro-web/src/views/overview/index.vue
index fe0089338..e1d110b79 100644
--- a/amoro-web/src/views/overview/index.vue
+++ b/amoro-web/src/views/overview/index.vue
@@ -20,6 +20,7 @@ limitations under the License.
 import { onMounted, ref } from 'vue'
 import { useI18n } from 'vue-i18n'
 import SingleDataCard from './components/SingleDataCard.vue'
+import { usePageScroll } from '@/hooks/usePageScroll'
 import MultipleDataCard from './components/MultipleDataCard.vue'
 import Top10TablesCard from './components/Top10TablesCard.vue'
 import ResourceUsageCard from './components/ResourceUsageCard.vue'
@@ -30,6 +31,7 @@ import { bytesToSize } from '@/utils'
 import type { IKeyAndValue } from '@/types/common.type'
 
 const { t } = useI18n()
+const { pageScrollRef } = usePageScroll()
 
 const singleData = ref<IKeyAndValue[]>([])
 const multipleData = ref<IKeyAndValue[]>([])
@@ -54,29 +56,35 @@ onMounted(() => {
 </script>
 
 <template>
-  <div :style="{ background: '#F8F7F8', padding: '24px', minHeight: '900px' }" 
class="overview-content">
-    <a-row :gutter="[16, 8]">
-      <a-col v-for="(card, index) in singleData" :key="index" :span="6">
-        <SingleDataCard :title="t(card.key)" :value="card.value" />
-      </a-col>
-      <a-col :span="6">
-        <MultipleDataCard :title="t('resource')" :data="multipleData" />
-      </a-col>
-      <a-col :span="12">
-        <ResourceUsageCard />
-      </a-col>
-      <a-col :span="12">
-        <DataSizeCard />
-      </a-col>
-      <a-col :span="12">
-        <PieChartCard :title="t('optimizingStatus')" 
:data="optimizingStatusData" />
-      </a-col>
-      <a-col :span="12">
-        <Top10TablesCard />
-      </a-col>
-    </a-row>
+  <div class="page-scroll" ref="pageScrollRef">
+    <div :style="{ background: '#F8F7F8', padding: '24px', minHeight: '900px' 
}" class="overview-content">
+      <a-row :gutter="[16, 8]">
+        <a-col v-for="(card, index) in singleData" :key="index" :span="6">
+          <SingleDataCard :title="t(card.key)" :value="card.value" />
+        </a-col>
+        <a-col :span="6">
+          <MultipleDataCard :title="t('resource')" :data="multipleData" />
+        </a-col>
+        <a-col :span="12">
+          <ResourceUsageCard />
+        </a-col>
+        <a-col :span="12">
+          <DataSizeCard />
+        </a-col>
+        <a-col :span="12">
+          <PieChartCard :title="t('optimizingStatus')" 
:data="optimizingStatusData" />
+        </a-col>
+        <a-col :span="12">
+          <Top10TablesCard />
+        </a-col>
+      </a-row>
+    </div>
   </div>
 </template>
 
 <style lang="less" scoped>
+.page-scroll {
+  height: 100%;
+  overflow-y: auto;
+}
 </style>
diff --git a/amoro-web/src/views/resource/index.vue 
b/amoro-web/src/views/resource/index.vue
index d4ab7ab4f..a3fd96545 100644
--- a/amoro-web/src/views/resource/index.vue
+++ b/amoro-web/src/views/resource/index.vue
@@ -34,6 +34,7 @@ import { usePagination } from '@/hooks/usePagination'
 import type { IIOptimizeGroupItem, ILableAndValue } from '@/types/common.type'
 import GroupModal from '@/views/resource/components/GroupModal.vue'
 import CreateOptimizerModal from 
'@/views/resource/components/CreateOptimizerModal.vue'
+import { usePageScroll } from '@/hooks/usePageScroll'
 
 export default defineComponent({
   name: 'Resource',
@@ -47,10 +48,12 @@ export default defineComponent({
     const { t } = useI18n()
     const router = useRouter()
     const route = useRoute()
+    const { pageScrollRef } = usePageScroll()
     const tabConfig: ILableAndValue[] = shallowReactive([
       { label: t('optimizerGroups'), value: 'optimizerGroups' },
       { label: t('optimizers'), value: 'optimizers' },
     ])
+
     const placeholder = reactive(usePlaceholder())
     const pagination = reactive(usePagination())
     const state = reactive({
@@ -127,81 +130,88 @@ export default defineComponent({
       editGroup,
       createOptimizer,
       t,
+      pageScrollRef,
     }
   },
 })
 </script>
 
 <template>
-  <div class="border-wrap">
-    <div class="resource-wrap">
-      <div class="content">
-        <a-tabs
-          v-model:activeKey="activeTab"
-          destroy-inactive-tab-pane
-          @change="onChangeTab"
-        >
-          <a-tab-pane
-            key="tables"
-            :tab="t('tables')"
-            :class="[activeTab === 'tables' ? 'active' : '']"
-          >
-            <TableList />
-          </a-tab-pane>
-          <a-tab-pane
-            key="optimizers"
-            :tab="t('optimizers')"
-            :class="[activeTab === 'optimizers' ? 'active' : '']"
-          >
-            <a-button type="primary" class="g-mb-16" 
@click="createOptimizer(null)">
-              {{ t("createOptimizer") }}
-            </a-button>
-            <List type="optimizers" />
-          </a-tab-pane>
-          <a-tab-pane
-            key="optimizerGroups"
-            :tab="t('optimizerGroups')"
-            :class="[activeTab === 'optimizerGroups' ? 'active' : '']"
+  <div class="page-scroll" ref="pageScrollRef">
+    <div class="border-wrap">
+      <div class="resource-wrap">
+        <div class="content">
+          <a-tabs
+            v-model:activeKey="activeTab"
+            destroy-inactive-tab-pane
+            @change="onChangeTab"
           >
-            <a-button type="primary" class="g-mb-16" @click="editGroup(null)">
-              {{ t("addGroup") }}
-            </a-button>
-            <List
-              :key="groupKeyCount"
-              type="optimizerGroups"
-              @edit-group="editGroup"
-            />
-          </a-tab-pane>
-        </a-tabs>
+            <a-tab-pane
+              key="tables"
+              :tab="t('tables')"
+              :class="[activeTab === 'tables' ? 'active' : '']"
+            >
+              <TableList />
+            </a-tab-pane>
+            <a-tab-pane
+              key="optimizers"
+              :tab="t('optimizers')"
+              :class="[activeTab === 'optimizers' ? 'active' : '']"
+            >
+              <a-button type="primary" class="g-mb-16" 
@click="createOptimizer(null)">
+                {{ t("createOptimizer") }}
+              </a-button>
+              <List type="optimizers" />
+            </a-tab-pane>
+            <a-tab-pane
+              key="optimizerGroups"
+              :tab="t('optimizerGroups')"
+              :class="[activeTab === 'optimizerGroups' ? 'active' : '']"
+            >
+              <a-button type="primary" class="g-mb-16" 
@click="editGroup(null)">
+                {{ t("addGroup") }}
+              </a-button>
+              <List
+                :key="groupKeyCount"
+                type="optimizerGroups"
+                @edit-group="editGroup"
+              />
+            </a-tab-pane>
+          </a-tabs>
+        </div>
       </div>
+      <GroupModal
+        v-if="showGroupModal"
+        :edit="groupEdit"
+        :edit-record="groupEditRecord"
+        @cancel="showGroupModal = false"
+        @refresh="
+          groupKeyCount++;
+          showGroupModal = false;
+        "
+      />
+      <CreateOptimizerModal
+        v-if="showCreateOptimizer"
+        @cancel="showCreateOptimizer = false"
+        @refresh="showCreateOptimizer = false"
+      />
     </div>
-    <GroupModal
-      v-if="showGroupModal"
-      :edit="groupEdit"
-      :edit-record="groupEditRecord"
-      @cancel="showGroupModal = false"
-      @refresh="
-        groupKeyCount++;
-        showGroupModal = false;
-      "
-    />
-    <CreateOptimizerModal
-      v-if="showCreateOptimizer"
-      @cancel="showCreateOptimizer = false"
-      @refresh="showCreateOptimizer = false"
-    />
   </div>
 </template>
 
 <style lang="less" scoped>
-.border-wrap {
-  padding: 16px 24px;
-  height: 100%;
-}
-.resource-wrap {
+.page-scroll {
   height: 100%;
   overflow-y: auto;
-  .status-icon {
+}
+ .border-wrap {
+   padding: 16px 24px;
+   height: 100%;
+ }
+ .resource-wrap {
+   height: 100%;
+  /* overflow-y: auto; */
+   .status-icon {
     width: 8px;
     height: 8px;
     border-radius: 8px;
diff --git a/amoro-web/src/views/settings/index.vue 
b/amoro-web/src/views/settings/index.vue
index cca9085d7..5f4f23960 100644
--- a/amoro-web/src/views/settings/index.vue
+++ b/amoro-web/src/views/settings/index.vue
@@ -22,10 +22,12 @@ import { useRoute, useRouter } from 'vue-router'
 import { useI18n } from 'vue-i18n'
 import type { IColumns, IContainerSetting, IKeyAndValue } from 
'@/types/common.type'
 import { getContainersSetting, getSystemSetting } from 
'@/services/setting.services'
+import { usePageScroll } from '@/hooks/usePageScroll'
 
 const { t } = useI18n()
 const router = useRouter()
 const route = useRoute()
+const { pageScrollRef } = usePageScroll()
 const loading = ref<boolean>(false)
 const systemSettingArray = reactive<IKeyAndValue[]>([])
 const containerSetting = reactive<IContainerSetting[]>([])
@@ -117,73 +119,78 @@ function getSettingInfo() {
 </script>
 
 <template>
-  <div class="setting-wrap">
-    <a-tabs v-model:activeKey="activeKeyTab" @change="onChangeTab">
-      <a-tab-pane :key="tabMap.system.key" :tab="tabMap.system.title">
-        <div class="system-setting">
-          <a-table
-            v-if="systemSettingArray.length"
-            row-key="key"
-            :columns="basicColumns"
-            :data-source="systemSettingArray"
-            :pagination="false"
-          />
-        </div>
-      </a-tab-pane>
-      <a-tab-pane :key="tabMap.container.key" :tab="tabMap.container.title">
-        <div class="container-setting">
-          <a-collapse v-model:activeKey="activeKey">
-            <a-collapse-panel v-for="container in containerSetting" 
:key="container.name" :header="container.name">
-              <ul class="content">
-                <li class="item">
-                  <h3 class="left">
-                    {{ $t('name') }}
-                  </h3>
-                  <span class="right">{{ container.name }}</span>
-                </li>
-                <li v-if="container.classpath" class="item">
-                  <h3 class="left">
-                    {{ $t('implementation') }}
-                  </h3>
-                  <span class="right">{{ container.classpath }}</span>
-                </li>
-              </ul>
-              <h3 class="g-mb-12 g-mt-12">
-                {{ $t('properties') }}
-              </h3>
-              <a-table
-                row-key="key"
-                :columns="basicColumns"
-                :data-source="container.propertiesArray"
-                :pagination="false"
-              />
-              <h3 class="g-mb-12 g-mt-12">
-                {{ $t('optimizerGroups') }}
-              </h3>
-              <a-collapse>
-                <a-collapse-panel v-for="innerGroup in 
container.optimizeGroup" :key="innerGroup.name" :header="innerGroup.name">
-                  <a-table
-                    row-key="name"
-                    :columns="basicColumns"
-                    :data-source="innerGroup.innerPropertiesArray"
-                    :pagination="false"
-                  />
-                </a-collapse-panel>
-              </a-collapse>
-            </a-collapse-panel>
-          </a-collapse>
-        </div>
-      </a-tab-pane>
-    </a-tabs>
+  <div class="page-scroll" ref="pageScrollRef">
+    <div class="setting-wrap">
+      <a-tabs v-model:activeKey="activeKeyTab" @change="onChangeTab">
+        <a-tab-pane :key="tabMap.system.key" :tab="tabMap.system.title">
+          <div class="system-setting">
+            <a-table
+              v-if="systemSettingArray.length"
+              row-key="key"
+              :columns="basicColumns"
+              :data-source="systemSettingArray"
+              :pagination="false"
+            />
+          </div>
+        </a-tab-pane>
+        <a-tab-pane :key="tabMap.container.key" :tab="tabMap.container.title">
+          <div class="container-setting">
+            <a-collapse v-model:activeKey="activeKey">
+              <a-collapse-panel v-for="container in containerSetting" 
:key="container.name" :header="container.name">
+                <ul class="content">
+                  <li class="item">
+                    <h3 class="left">
+                      {{ $t('name') }}
+                    </h3>
+                    <span class="right">{{ container.name }}</span>
+                  </li>
+                  <li v-if="container.classpath" class="item">
+                    <h3 class="left">
+                      {{ $t('implementation') }}
+                    </h3>
+                    <span class="right">{{ container.classpath }}</span>
+                  </li>
+                </ul>
+                <h3 class="g-mb-12 g-mt-12">
+                  {{ $t('properties') }}
+                </h3>
+                <a-table
+                  row-key="key"
+                  :columns="basicColumns"
+                  :data-source="container.propertiesArray"
+                  :pagination="false"
+                />
+                <h3 class="g-mb-12 g-mt-12">
+                  {{ $t('optimizerGroups') }}
+                </h3>
+                <a-collapse>
+                  <a-collapse-panel v-for="innerGroup in 
container.optimizeGroup" :key="innerGroup.name" :header="innerGroup.name">
+                    <a-table
+                      row-key="name"
+                      :columns="basicColumns"
+                      :data-source="innerGroup.innerPropertiesArray"
+                      :pagination="false"
+                    />
+                  </a-collapse-panel>
+                </a-collapse>
+              </a-collapse-panel>
+            </a-collapse>
+          </div>
+        </a-tab-pane>
+      </a-tabs>
+    </div>
   </div>
   <u-loading v-if="loading" />
 </template>
 
 <style lang="less" scoped>
-.setting-wrap {
+.page-scroll {
   height: 100%;
-  overflow: auto;
-  padding: 16px 24px;
+  overflow-y: auto;
+}
+ .setting-wrap {
+   height: 100%;
+   padding: 16px 24px;
   h1,h2,h3 {
     font-weight: 500;
   }
diff --git a/amoro-web/src/views/tables/components/TableExplorer.vue 
b/amoro-web/src/views/tables/components/TableExplorer.vue
index fb6d5b68e..fdc528c41 100755
--- a/amoro-web/src/views/tables/components/TableExplorer.vue
+++ b/amoro-web/src/views/tables/components/TableExplorer.vue
@@ -43,18 +43,10 @@ interface TreeNode {
   tableType?: string
 }
 
-interface StorageValue {
-  catalog?: string
-  database?: string
-  tableName?: string
-  type?: string
-}
-
 const router = useRouter()
 const route = useRoute()
 
 const storageTableKey = 'easylake-menu-catalog-db-table'
-const storageCataDBTable = JSON.parse(localStorage.getItem(storageTableKey) || 
'{}') as StorageValue
 const expandedKeysSessionKey = 'tables_expanded_keys'
 
 const state = reactive({
@@ -196,6 +188,42 @@ async function loadChildren(node: any) {
   }
 }
 
+async function expandPathBySelected(catalog: string, db: string) {
+  const safeCatalog = (catalog || '').trim()
+  const safeDb = (db || '').trim()
+
+  if (!safeCatalog) {
+    return
+  }
+
+  const nextExpandedKeys = new Set(state.expandedKeys)
+
+  const catalogKey = `catalog:${safeCatalog}`
+  const catalogNode = state.treeData.find(node => node.key === catalogKey)
+  if (catalogNode) {
+    await loadChildren({ dataRef: catalogNode })
+    nextExpandedKeys.add(catalogKey)
+  }
+
+  if (safeDb) {
+    const dbKey = `catalog:${safeCatalog}/db:${safeDb}`
+    const latestCatalogNode = state.treeData.find(node => node.key === 
catalogKey)
+    const dbNode = latestCatalogNode?.children?.find(child => child.key === 
dbKey)
+    if (dbNode) {
+      await loadChildren({ dataRef: dbNode })
+      nextExpandedKeys.add(dbKey)
+    }
+  }
+
+  state.expandedKeys = Array.from(nextExpandedKeys)
+  try {
+    sessionStorage.setItem(expandedKeysSessionKey, 
JSON.stringify(state.expandedKeys))
+  }
+  catch (e) {
+    // ignore sessionStorage write errors
+  }
+}
+
 function handleSelectTable(catalog: string, db: string, tableName: string, 
tableType: string) {
   if (!catalog || !db || !tableName) {
     return
@@ -424,6 +452,36 @@ watch(
   },
 )
 
+watch(
+  () => route.query,
+  async (value, oldValue) => {
+    const { catalog, db, table } = value as any
+    const { catalog: oldCatalog, db: oldDb, table: oldTable } = (oldValue || 
{}) as any
+
+    if (`${catalog || ''}${db || ''}${table || ''}` === `${oldCatalog || 
''}${oldDb || ''}${oldTable || ''}`) {
+      return
+    }
+
+    const catalogStr = (catalog as string) || ''
+    const dbStr = (db as string) || ''
+    const tableStr = (table as string) || ''
+
+    if (catalogStr && dbStr) {
+      await expandPathBySelected(catalogStr, dbStr)
+      if (tableStr) {
+        const tableKey = `catalog:${catalogStr}/db:${dbStr}/table:${tableStr}`
+        state.selectedKeys = [tableKey]
+      }
+      else {
+        state.selectedKeys = []
+      }
+    }
+    else {
+      state.selectedKeys = []
+    }
+  },
+)
+
 const searchResult = computed(() => {
   const keyword = normalizeKeyword(state.filterKey)
   if (!keyword) {
@@ -479,14 +537,16 @@ onBeforeMount(async () => {
     }
 
     state.expandedKeys = restoredExpandedKeys
+  }
 
-    // Select last visited table from route or local storage without 
auto-expanding tree
-    const query = route.query || {}
-    const queryCatalog = (query.catalog as string) || 
storageCataDBTable.catalog
-    const queryDb = (query.db as string) || storageCataDBTable.database
-    const queryTable = (query.table as string) || storageCataDBTable.tableName
+  const query = route.query || {}
+  const queryCatalog = (query.catalog as string) || ''
+  const queryDb = (query.db as string) || ''
+  const queryTable = (query.table as string) || ''
 
-    if (queryCatalog && queryDb && queryTable) {
+  if (queryCatalog && queryDb) {
+    await expandPathBySelected(queryCatalog, queryDb)
+    if (queryTable) {
       const tableKey = 
`catalog:${queryCatalog}/db:${queryDb}/table:${queryTable}`
       state.selectedKeys = [tableKey]
     }
diff --git a/amoro-web/src/views/tables/index.vue 
b/amoro-web/src/views/tables/index.vue
index 8fdd485f1..651d27b52 100644
--- a/amoro-web/src/views/tables/index.vue
+++ b/amoro-web/src/views/tables/index.vue
@@ -28,6 +28,7 @@ import UHealthScore from './components/HealthScoreDetails.vue'
 import TableExplorer from './components/TableExplorer.vue'
 import useStore from '@/store/index'
 import type { IBaseDetailInfo } from '@/types/common.type'
+import { usePageScroll } from '@/hooks/usePageScroll'
 
 export default defineComponent({
   name: 'Tables',
@@ -46,6 +47,7 @@ export default defineComponent({
     const store = useStore()
 
     const detailRef = ref()
+    const { pageScrollRef } = usePageScroll()
 
     const SIDEBAR_WIDTH_STORAGE_KEY = 'tables_sidebar_width'
     const SIDEBAR_MIN_WIDTH = 320
@@ -187,6 +189,7 @@ export default defineComponent({
     onMounted(() => {
       initSidebarWidth()
       state.activeKey = (route.query?.tab as string) || 'Details'
+
       nextTick(() => {
         if (detailRef.value && hasSelectedTable.value) {
           detailRef.value.getTableDetails()
@@ -211,75 +214,82 @@ export default defineComponent({
       onChangeTab,
       sidebarWidth,
       startSidebarResize,
+      pageScrollRef,
     }
   },
 })
 </script>
 
 <template>
-  <div class="tables-wrap">
-    <div v-if="!isSecondaryNav" class="tables-content">
-      <div
-        class="tables-sidebar"
-        :style="{ width: `${sidebarWidth}px`, flex: `0 0 ${sidebarWidth}px` }"
-      >
-        <TableExplorer />
-      </div>
-      <div class="tables-divider" aria-hidden="true" 
@mousedown="startSidebarResize" />
-      <div class="tables-main">
-        <template v-if="hasSelectedTable">
-          <div class="tables-main-header g-flex-jsb">
-            <div class="g-flex-col">
-              <div class="g-flex">
-                <span :title="baseInfo.tableName" class="table-name 
g-text-nowrap">{{ baseInfo.tableName }}</span>
-              </div>
-              <div v-if="baseInfo.comment" class="table-info g-flex-ac">
-                <p>{{ $t('Comment') }}: <span class="text-color">{{ 
baseInfo.comment }}</span></p>
-              </div>
-              <div class="table-info g-flex-ac">
-                <p>{{ $t('optimizingStatus') }}: <span class="text-color">{{ 
baseInfo.optimizingStatus }}</span></p>
-                <a-divider type="vertical" />
-                <p>{{ $t('records') }}: <span class="text-color">{{ 
baseInfo.records }}</span></p>
-                <a-divider type="vertical" />
-                <template v-if="!isIceberg">
-                  <p>{{ $t('createTime') }}: <span class="text-color">{{ 
baseInfo.createTime }}</span></p>
+  <div class="page-scroll" ref="pageScrollRef">
+    <div class="tables-wrap">
+      <div v-if="!isSecondaryNav" class="tables-content">
+        <div
+          class="tables-sidebar"
+          :style="{ width: `${sidebarWidth}px`, flex: `0 0 ${sidebarWidth}px` 
}"
+        >
+          <TableExplorer />
+        </div>
+        <div class="tables-divider" aria-hidden="true" 
@mousedown="startSidebarResize" />
+        <div class="tables-main">
+          <template v-if="hasSelectedTable">
+            <div class="tables-main-header g-flex-jsb">
+              <div class="g-flex-col">
+                <div class="g-flex">
+                  <span :title="baseInfo.tableName" class="table-name 
g-text-nowrap">{{ baseInfo.tableName }}</span>
+                </div>
+                <div v-if="baseInfo.comment" class="table-info g-flex-ac">
+                  <p>{{ $t('Comment') }}: <span class="text-color">{{ 
baseInfo.comment }}</span></p>
+                </div>
+                <div class="table-info g-flex-ac">
+                  <p>{{ $t('optimizingStatus') }}: <span class="text-color">{{ 
baseInfo.optimizingStatus }}</span></p>
+                  <a-divider type="vertical" />
+                  <p>{{ $t('records') }}: <span class="text-color">{{ 
baseInfo.records }}</span></p>
                   <a-divider type="vertical" />
-                </template>
-                <p>{{ $t('tableFormat') }}: <span class="text-color">{{ 
baseInfo.tableFormat }}</span></p>
-                <a-divider type="vertical" />
-                <p>
-                  {{ $t('healthScore') }}:
-                  <UHealthScore :base-info="baseInfo" />
-                </p>
+                  <template v-if="!isIceberg">
+                    <p>{{ $t('createTime') }}: <span class="text-color">{{ 
baseInfo.createTime }}</span></p>
+                    <a-divider type="vertical" />
+                  </template>
+                  <p>{{ $t('tableFormat') }}: <span class="text-color">{{ 
baseInfo.tableFormat }}</span></p>
+                  <a-divider type="vertical" />
+                  <p>
+                    {{ $t('healthScore') }}:
+                    <UHealthScore :base-info="baseInfo" />
+                  </p>
+                </div>
               </div>
             </div>
-          </div>
-          <div class="tables-main-body">
-            <a-tabs v-model:activeKey="activeKey" destroy-inactive-tab-pane 
@change="onChangeTab">
-              <a-tab-pane key="Details" :tab="$t('details')" force-render>
-                <UDetails ref="detailRef" 
@set-base-detail-info="setBaseDetailInfo" />
-              </a-tab-pane>
-              <a-tab-pane v-if="detailLoaded" key="Files" :tab="$t('files')">
-                <UFiles :has-partition="baseInfo.hasPartition" />
-              </a-tab-pane>
-              <a-tab-pane v-for="tab in tabConfigs" :key="tab.key" 
:tab="$t(tab.label)">
-                <component :is="`U${tab.key}`" />
-              </a-tab-pane>
-            </a-tabs>
-          </div>
-        </template>
-        <div v-else class="empty-page" />
+            <div class="tables-main-body">
+              <a-tabs v-model:activeKey="activeKey" destroy-inactive-tab-pane 
@change="onChangeTab">
+                <a-tab-pane key="Details" :tab="$t('details')" force-render>
+                  <UDetails ref="detailRef" 
@set-base-detail-info="setBaseDetailInfo" />
+                </a-tab-pane>
+                <a-tab-pane v-if="detailLoaded" key="Files" :tab="$t('files')">
+                  <UFiles :has-partition="baseInfo.hasPartition" />
+                </a-tab-pane>
+                <a-tab-pane v-for="tab in tabConfigs" :key="tab.key" 
:tab="$t(tab.label)">
+                  <component :is="`U${tab.key}`" />
+                </a-tab-pane>
+              </a-tabs>
+            </div>
+          </template>
+          <div v-else class="empty-page" />
+        </div>
       </div>
+      <!-- Create table secondary page -->
+      <router-view v-else @go-back="goBack" />
     </div>
-    <!-- Create table secondary page -->
-    <router-view v-else @go-back="goBack" />
   </div>
 </template>
 
 <style lang="less" scoped>
+.page-scroll {
+  height: 100%;
+  overflow-y: auto;
+}
 .tables-wrap {
-  font-size: 14px;
-  border: 1px solid #e8e8f0;
+   font-size: 14px;
+   border: 1px solid #e8e8f0;
   padding: 12px 0;
   height: 100%;
   min-height: 100%;
diff --git a/amoro-web/src/views/terminal/index.vue 
b/amoro-web/src/views/terminal/index.vue
index 39cd3ffe1..4e696c56a 100644
--- a/amoro-web/src/views/terminal/index.vue
+++ b/amoro-web/src/views/terminal/index.vue
@@ -27,6 +27,7 @@ import { debugResultBgcMap } from '@/types/common.type'
 import { executeSql, getExampleSqlCode, getJobDebugResult, getLastDebugInfo, 
getLogsResult, getShortcutsList, stopSql } from '@/services/terminal.service'
 import { getCatalogList } from '@/services/table.service'
 import { usePlaceholder } from '@/hooks/usePlaceholder'
+import { usePageScroll } from '@/hooks/usePageScroll'
 
 interface ISessionInfo {
   sessionId: string
@@ -50,6 +51,7 @@ export default defineComponent({
     const loading = ref<boolean>(false)
     const sqlEditorRef = ref<any>(null)
     const sqlLogRef = ref<any>(null)
+    const { pageScrollRef } = usePageScroll()
     const readOnly = ref<boolean>(false)
     const sqlSource = ref<string>('')
     const showDebug = ref<boolean>(false)
@@ -339,133 +341,140 @@ export default defineComponent({
       sqlResultHeight,
       dragMounseDown,
       changeUseCatalog,
-    }
-  },
-})
+      pageScrollRef,
+     }
+   },
+ })
 </script>
 
 <template>
-  <div class="console-wrap">
-    <div class="console-content" :class="{ fullscreen }">
-      <div :style="{ height: `${sqlResultHeight}px` }" class="sql-wrap">
-        <div class="sql-block">
-          <div class="top-ops g-flex-jsb">
-            <div class="title-left g-flex-ac">
-              <div class="select-catalog g-mr-12">
-                <span class="label">{{ $t('use') }}</span>
-                <a-select
-                  v-model:value="curCatalog" style="width: 200px" 
:options="catalogOptions"
-                  @change="changeUseCatalog"
-                />
+  <div class="page-scroll" ref="pageScrollRef">
+    <div class="console-wrap">
+      <div class="console-content" :class="{ fullscreen }">
+        <div :style="{ height: `${sqlResultHeight}px` }" class="sql-wrap">
+          <div class="sql-block">
+            <div class="top-ops g-flex-jsb">
+              <div class="title-left g-flex-ac">
+                <div class="select-catalog g-mr-12">
+                  <span class="label">{{ $t('use') }}</span>
+                  <a-select
+                    v-model:value="curCatalog" style="width: 200px" 
:options="catalogOptions"
+                    @change="changeUseCatalog"
+                  />
+                </div>
+                <a-tooltip v-if="runStatus === 'Running'" :title="$t('pause')" 
placement="bottom">
+                  <svg-icon
+                    class-name="icon-svg" icon-class="sqlpause" 
class="g-mr-12" :disabled="readOnly"
+                    @click="handleIconClick('pause')"
+                  />
+                </a-tooltip>
+                <a-tooltip v-else :title="$t('run')" placement="bottom">
+                  <svg-icon
+                    class-name="icon-svg" icon-class="sqldebug" 
class="g-mr-12" :disabled="readOnly"
+                    @click="handleIconClick('debug')"
+                  />
+                </a-tooltip>
+                <a-tooltip :title="$t('format')" placement="bottom">
+                  <svg-icon
+                    class-name="icon-svg" :is-stroke="true" 
icon-class="format" :disabled="readOnly"
+                    @click="handleIconClick('format')"
+                  />
+                </a-tooltip>
+              </div>
+              <div class="title-right">
+                <a-tooltip
+                  :title="fullscreen ? $t('recovery') : $t('fullscreen')" 
placement="bottom"
+                  :get-popup-container="getPopupContainer"
+                >
+                  <svg-icon
+                    class-name="icon-svg" :is-stroke="true" 
:icon-class="fullscreen ? 'sqlinit' : 'sqlmax'"
+                    :disabled="false" class="g-ml-12" @click="handleFull"
+                  />
+                </a-tooltip>
               </div>
-              <a-tooltip v-if="runStatus === 'Running'" :title="$t('pause')" 
placement="bottom">
-                <svg-icon
-                  class-name="icon-svg" icon-class="sqlpause" class="g-mr-12" 
:disabled="readOnly"
-                  @click="handleIconClick('pause')"
-                />
-              </a-tooltip>
-              <a-tooltip v-else :title="$t('run')" placement="bottom">
-                <svg-icon
-                  class-name="icon-svg" icon-class="sqldebug" class="g-mr-12" 
:disabled="readOnly"
-                  @click="handleIconClick('debug')"
-                />
-              </a-tooltip>
-              <a-tooltip :title="$t('format')" placement="bottom">
-                <svg-icon
-                  class-name="icon-svg" :is-stroke="true" icon-class="format" 
:disabled="readOnly"
-                  @click="handleIconClick('format')"
-                />
-              </a-tooltip>
             </div>
-            <div class="title-right">
-              <a-tooltip
-                :title="fullscreen ? $t('recovery') : $t('fullscreen')" 
placement="bottom"
-                :get-popup-container="getPopupContainer"
-              >
-                <svg-icon
-                  class-name="icon-svg" :is-stroke="true" 
:icon-class="fullscreen ? 'sqlinit' : 'sqlmax'"
-                  :disabled="false" class="g-ml-12" @click="handleFull"
+            <div class="sql-content">
+              <div class="sql-raw">
+                <SqlEditor
+                  ref="sqlEditorRef" v-model:value="sqlSource" 
:sql-value="sqlSource" :read-only="readOnly"
+                  :options="{
+                    readOnly,
+                    minimap: { enabled: false },
+                  }"
                 />
-              </a-tooltip>
+              </div>
             </div>
           </div>
-          <div class="sql-content">
-            <div class="sql-raw">
-              <SqlEditor
-                ref="sqlEditorRef" v-model:value="sqlSource" 
:sql-value="sqlSource" :read-only="readOnly"
-                :options="{
-                  readOnly,
-                  minimap: { enabled: false },
-                }"
-              />
+          <div class="sql-shortcuts">
+            <div class="shortcuts">
+              {{ $t('sqlShortcuts') }}
             </div>
+            <a-button
+              v-for="code in shortcuts" :key="code" type="link"
+              :disabled="runStatus === 'Running' || runStatus === 'Canceling'" 
class="code" @click="generateCode(code)"
+            >
+              {{
+                code
+              }}
+            </a-button>
           </div>
         </div>
-        <div class="sql-shortcuts">
-          <div class="shortcuts">
-            {{ $t('sqlShortcuts') }}
-          </div>
-          <a-button
-            v-for="code in shortcuts" :key="code" type="link"
-            :disabled="runStatus === 'Running' || runStatus === 'Canceling'" 
class="code" @click="generateCode(code)"
-          >
-            {{
-              code
-            }}
-          </a-button>
-        </div>
-      </div>
 
-      <div v-if="runStatus" class="run-status" :style="{ background: 
bgcMap[runStatus] }">
-        <template v-if="runStatus === 'Running' || runStatus === 'Canceling'">
-          <loading-outlined style="color: #1890ff" />
-        </template>
-        <template v-if="runStatus === 'Canceled' || runStatus === 'Failed'">
-          <close-circle-outlined style="color: #ff4d4f" />
-        </template>
-        <template v-if="runStatus === 'Finished'">
-          <check-circle-outlined style="color: #52c41a" />
-        </template>
-        <template v-if="runStatus === 'Created'">
-          <close-circle-outlined style="color:#333" />
-        </template>
-        <span class="g-ml-12">{{ $t(runStatus) }}</span>
-      </div>
+        <div v-if="runStatus" class="run-status" :style="{ background: 
bgcMap[runStatus] }">
+          <template v-if="runStatus === 'Running' || runStatus === 
'Canceling'">
+            <loading-outlined style="color: #1890ff" />
+          </template>
+          <template v-if="runStatus === 'Canceled' || runStatus === 'Failed'">
+            <close-circle-outlined style="color: #ff4d4f" />
+          </template>
+          <template v-if="runStatus === 'Finished'">
+            <check-circle-outlined style="color: #52c41a" />
+          </template>
+          <template v-if="runStatus === 'Created'">
+            <close-circle-outlined style="color:#333" />
+          </template>
+          <span class="g-ml-12">{{ $t(runStatus) }}</span>
+        </div>
 
-      <!-- sql result -->
-      <div
-        class="sql-result" :style="{ height: `calc(100% - 
${sqlResultHeight}px)` }"
-        :class="resultFullscreen ? 'result-full' : ''"
-      >
-        <span class="drag-line" @mousedown="dragMounseDown"><svg-icon 
class="icon" icon-class="slide" /></span>
-        <div class="tab-operation">
-          <div class="tab">
-            <span
-              :class="{ active: operationActive === 'log' }" class="tab-item"
-              @click="operationActive = 'log'"
-            >{{ $t('log') }}</span>
-            <span
-              v-for="item in resultTabList" :key="item.id" :class="{ active: 
operationActive === item.id }"
-              class="tab-item" @click="operationActive = item.id"
-            >{{ item.id }}</span>
+        <!-- sql result -->
+        <div
+          class="sql-result" :style="{ height: `calc(100% - 
${sqlResultHeight}px)` }"
+          :class="resultFullscreen ? 'result-full' : ''"
+        >
+          <span class="drag-line" @mousedown="dragMounseDown"><svg-icon 
class="icon" icon-class="slide" /></span>
+          <div class="tab-operation">
+            <div class="tab">
+              <span
+                :class="{ active: operationActive === 'log' }" class="tab-item"
+                @click="operationActive = 'log'"
+              >{{ $t('log') }}</span>
+              <span
+                v-for="item in resultTabList" :key="item.id" :class="{ active: 
operationActive === item.id }"
+                class="tab-item" @click="operationActive = item.id"
+              >{{ item.id }}</span>
+            </div>
+          </div>
+          <div class="debug-result">
+            <SqlLog v-show="operationActive === 'log'" ref="sqlLogRef" />
+            <template v-for="item in resultTabList" :key="item.id">
+              <SqlResult v-if="operationActive === item.id" :info="item" />
+            </template>
           </div>
-        </div>
-        <div class="debug-result">
-          <SqlLog v-show="operationActive === 'log'" ref="sqlLogRef" />
-          <template v-for="item in resultTabList" :key="item.id">
-            <SqlResult v-if="operationActive === item.id" :info="item" />
-          </template>
         </div>
       </div>
+      <u-loading v-if="loading" />
     </div>
-    <u-loading v-if="loading" />
   </div>
 </template>
 
 <style lang="less" scoped>
-.console-wrap {
+.page-scroll {
   height: 100%;
-  padding: 16px 24px;
+  overflow-y: auto;
+}
+ .console-wrap {
+   height: 100%;
+   padding: 16px 24px;
 
   .console-content {
     background-color: #fff;

Reply via email to