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;