feat: 优化知识库分块操作区与滚动导航
- 将向量化操作上移到分块预览前的统一操作区 - 增加页顶页末双气泡并完善边界禁用状态
This commit is contained in:
@@ -51,6 +51,8 @@
|
|||||||
"submitPublishApproval": "Submit Publish Approval",
|
"submitPublishApproval": "Submit Publish Approval",
|
||||||
"viewSegmentation": "View Segments",
|
"viewSegmentation": "View Segments",
|
||||||
"continueProcess": "Continue",
|
"continueProcess": "Continue",
|
||||||
|
"backToTop": "Back to top",
|
||||||
|
"goToBottom": "Go to bottom",
|
||||||
"startIndex": "Start Indexing",
|
"startIndex": "Start Indexing",
|
||||||
"retryParse": "Retry Parse",
|
"retryParse": "Retry Parse",
|
||||||
"retryIndex": "Retry Index"
|
"retryIndex": "Retry Index"
|
||||||
|
|||||||
@@ -51,6 +51,8 @@
|
|||||||
"submitPublishApproval": "提交发布审批",
|
"submitPublishApproval": "提交发布审批",
|
||||||
"viewSegmentation": "查看分段",
|
"viewSegmentation": "查看分段",
|
||||||
"continueProcess": "继续处理",
|
"continueProcess": "继续处理",
|
||||||
|
"backToTop": "回到页顶",
|
||||||
|
"goToBottom": "到达页末",
|
||||||
"startIndex": "开始向量化",
|
"startIndex": "开始向量化",
|
||||||
"retryParse": "重试解析",
|
"retryParse": "重试解析",
|
||||||
"retryIndex": "重试向量化"
|
"retryIndex": "重试向量化"
|
||||||
|
|||||||
@@ -432,19 +432,7 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
|
|
||||||
<section class="workbench__content">
|
<div class="workbench__actions">
|
||||||
<SplitterDocPreview
|
|
||||||
:loading="previewLoading"
|
|
||||||
:preview-items="previewItems"
|
|
||||||
@preview-session-change="handlePreviewSessionChange"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div v-if="previewError" class="workbench__error">
|
|
||||||
{{ previewError }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="workbench__footer">
|
|
||||||
<ElButton :disabled="startLoading" @click="handleCancel">
|
<ElButton :disabled="startLoading" @click="handleCancel">
|
||||||
{{ $t('button.cancel') }}
|
{{ $t('button.cancel') }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
@@ -457,6 +445,18 @@ watch(
|
|||||||
{{ $t('button.startIndex') }}
|
{{ $t('button.startIndex') }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<section class="workbench__content">
|
||||||
|
<SplitterDocPreview
|
||||||
|
:loading="previewLoading"
|
||||||
|
:preview-items="previewItems"
|
||||||
|
@preview-session-change="handlePreviewSessionChange"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div v-if="previewError" class="workbench__error">
|
||||||
|
{{ previewError }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -484,6 +484,15 @@ watch(
|
|||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workbench__actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
.workbench__content {
|
.workbench__content {
|
||||||
min-height: 620px;
|
min-height: 620px;
|
||||||
}
|
}
|
||||||
@@ -500,22 +509,6 @@ watch(
|
|||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workbench__footer {
|
|
||||||
position: sticky;
|
|
||||||
bottom: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 16px 0 8px;
|
|
||||||
margin-top: auto;
|
|
||||||
background: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
rgb(246 248 251 / 0%) 0%,
|
|
||||||
rgb(246 248 251 / 82%) 30%,
|
|
||||||
rgb(246 248 251 / 100%) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-form-item__label) {
|
:deep(.el-form-item__label) {
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export {
|
export {
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
|
ArrowDownToLine,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowLeftToLine,
|
ArrowLeftToLine,
|
||||||
ArrowRightLeft,
|
ArrowRightLeft,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { BacktopProps } from './backtop';
|
|||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { ArrowUpToLine } from '@easyflow-core/icons';
|
import { ArrowDownToLine, ArrowUpToLine } from '@easyflow-core/icons';
|
||||||
|
|
||||||
import { EasyFlowButton } from '../button';
|
import { EasyFlowButton } from '../button';
|
||||||
import { useBackTop } from './use-backtop';
|
import { useBackTop } from './use-backtop';
|
||||||
@@ -14,9 +14,11 @@ defineOptions({ name: 'BackTop' });
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
bottom: 20,
|
bottom: 20,
|
||||||
|
bottomLabel: 'Scroll to bottom',
|
||||||
isGroup: false,
|
isGroup: false,
|
||||||
right: 24,
|
right: 24,
|
||||||
target: '',
|
target: '',
|
||||||
|
topLabel: 'Back to top',
|
||||||
visibilityHeight: 200,
|
visibilityHeight: 200,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -25,19 +27,45 @@ const backTopStyle = computed(() => ({
|
|||||||
right: `${props.right}px`,
|
right: `${props.right}px`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { handleClick, visible } = useBackTop(props);
|
const {
|
||||||
|
canScrollDown,
|
||||||
|
canScrollUp,
|
||||||
|
handleBottomClick,
|
||||||
|
handleClick,
|
||||||
|
scrollable,
|
||||||
|
visible,
|
||||||
|
} = useBackTop(props);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<transition name="fade-down">
|
<transition name="fade-down">
|
||||||
<EasyFlowButton
|
<div
|
||||||
v-if="visible"
|
v-if="isGroup ? scrollable : visible"
|
||||||
:style="backTopStyle"
|
:style="backTopStyle"
|
||||||
class="dark:bg-accent dark:hover:bg-heavy bg-background hover:bg-heavy data shadow-float z-popup fixed bottom-10 size-10 rounded-full duration-500"
|
class="z-popup fixed flex flex-col gap-2"
|
||||||
size="icon"
|
|
||||||
variant="icon"
|
|
||||||
@click="handleClick"
|
|
||||||
>
|
>
|
||||||
<ArrowUpToLine class="size-4" />
|
<EasyFlowButton
|
||||||
</EasyFlowButton>
|
:aria-label="topLabel"
|
||||||
|
:disabled="isGroup && !canScrollUp"
|
||||||
|
:title="topLabel"
|
||||||
|
class="dark:bg-accent dark:hover:bg-heavy bg-background hover:bg-heavy shadow-float size-10 rounded-full duration-150"
|
||||||
|
size="icon"
|
||||||
|
variant="icon"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<ArrowUpToLine class="size-4" />
|
||||||
|
</EasyFlowButton>
|
||||||
|
<EasyFlowButton
|
||||||
|
v-if="isGroup"
|
||||||
|
:aria-label="bottomLabel"
|
||||||
|
:disabled="!canScrollDown"
|
||||||
|
:title="bottomLabel"
|
||||||
|
class="dark:bg-accent dark:hover:bg-heavy bg-background hover:bg-heavy shadow-float size-10 rounded-full duration-150"
|
||||||
|
size="icon"
|
||||||
|
variant="icon"
|
||||||
|
@click="handleBottomClick"
|
||||||
|
>
|
||||||
|
<ArrowDownToLine class="size-4" />
|
||||||
|
</EasyFlowButton>
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,6 +6,20 @@ export const backtopProps = {
|
|||||||
default: 40,
|
default: 40,
|
||||||
type: Number,
|
type: Number,
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* @zh_CN scroll-to-bottom button label.
|
||||||
|
*/
|
||||||
|
bottomLabel: {
|
||||||
|
default: 'Scroll to bottom',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @zh_CN whether to show top and bottom controls as a group.
|
||||||
|
*/
|
||||||
|
isGroup: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* @zh_CN right distance.
|
* @zh_CN right distance.
|
||||||
*/
|
*/
|
||||||
@@ -20,6 +34,13 @@ export const backtopProps = {
|
|||||||
default: '',
|
default: '',
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* @zh_CN back-to-top button label.
|
||||||
|
*/
|
||||||
|
topLabel: {
|
||||||
|
default: 'Back to top',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* @zh_CN the button will not show until the scroll height reaches this value.
|
* @zh_CN the button will not show until the scroll height reaches this value.
|
||||||
*/
|
*/
|
||||||
@@ -31,8 +52,10 @@ export const backtopProps = {
|
|||||||
|
|
||||||
export interface BacktopProps {
|
export interface BacktopProps {
|
||||||
bottom?: number;
|
bottom?: number;
|
||||||
|
bottomLabel?: string;
|
||||||
isGroup?: boolean;
|
isGroup?: boolean;
|
||||||
right?: number;
|
right?: number;
|
||||||
target?: string;
|
target?: string;
|
||||||
|
topLabel?: string;
|
||||||
visibilityHeight?: number;
|
visibilityHeight?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,31 @@
|
|||||||
import type { BacktopProps } from './backtop';
|
import type { BacktopProps } from './backtop';
|
||||||
|
|
||||||
import { onMounted, ref, shallowRef } from 'vue';
|
import { nextTick, onMounted, ref, shallowRef } from 'vue';
|
||||||
|
|
||||||
import { useEventListener, useThrottleFn } from '@vueuse/core';
|
import {
|
||||||
|
useEventListener,
|
||||||
|
useResizeObserver,
|
||||||
|
useThrottleFn,
|
||||||
|
} from '@vueuse/core';
|
||||||
|
|
||||||
export const useBackTop = (props: BacktopProps) => {
|
export const useBackTop = (props: BacktopProps) => {
|
||||||
const el = shallowRef<HTMLElement>();
|
const el = shallowRef<HTMLElement>();
|
||||||
const container = shallowRef<Document | HTMLElement>();
|
const container = shallowRef<Document | HTMLElement>();
|
||||||
|
const resizeTarget = shallowRef<HTMLElement>();
|
||||||
|
const canScrollDown = ref(false);
|
||||||
|
const canScrollUp = ref(false);
|
||||||
|
const scrollable = ref(false);
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
if (el.value) {
|
if (el.value) {
|
||||||
|
const maxScrollTop = Math.max(
|
||||||
|
el.value.scrollHeight - el.value.clientHeight,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
scrollable.value = maxScrollTop > 1;
|
||||||
|
canScrollUp.value = el.value.scrollTop > 1;
|
||||||
|
canScrollDown.value = el.value.scrollTop < maxScrollTop - 1;
|
||||||
visible.value = el.value.scrollTop >= (props?.visibilityHeight ?? 0);
|
visible.value = el.value.scrollTop >= (props?.visibilityHeight ?? 0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -19,12 +34,24 @@ export const useBackTop = (props: BacktopProps) => {
|
|||||||
el.value?.scrollTo({ behavior: 'smooth', top: 0 });
|
el.value?.scrollTo({ behavior: 'smooth', top: 0 });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBottomClick = () => {
|
||||||
|
if (!el.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.value.scrollTo({
|
||||||
|
behavior: 'smooth',
|
||||||
|
top: Math.max(el.value.scrollHeight - el.value.clientHeight, 0),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleScrollThrottled = useThrottleFn(handleScroll, 300, true);
|
const handleScrollThrottled = useThrottleFn(handleScroll, 300, true);
|
||||||
|
|
||||||
useEventListener(container, 'scroll', handleScrollThrottled);
|
useEventListener(container, 'scroll', handleScrollThrottled);
|
||||||
|
useResizeObserver(resizeTarget, handleScrollThrottled);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
container.value = document;
|
container.value = document;
|
||||||
el.value = document.documentElement;
|
el.value = document.documentElement;
|
||||||
|
resizeTarget.value = document.body;
|
||||||
|
|
||||||
if (props.target) {
|
if (props.target) {
|
||||||
el.value = document.querySelector<HTMLElement>(props.target) ?? undefined;
|
el.value = document.querySelector<HTMLElement>(props.target) ?? undefined;
|
||||||
@@ -33,13 +60,17 @@ export const useBackTop = (props: BacktopProps) => {
|
|||||||
throw new Error(`target does not exist: ${props.target}`);
|
throw new Error(`target does not exist: ${props.target}`);
|
||||||
}
|
}
|
||||||
container.value = el.value;
|
container.value = el.value;
|
||||||
|
resizeTarget.value = el.value;
|
||||||
}
|
}
|
||||||
// Give visible an initial value, fix #13066
|
void nextTick(handleScroll);
|
||||||
handleScroll();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
canScrollDown,
|
||||||
|
canScrollUp,
|
||||||
|
handleBottomClick,
|
||||||
handleClick,
|
handleClick,
|
||||||
|
scrollable,
|
||||||
visible,
|
visible,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -425,7 +425,11 @@ const headerSlots = computed(() => {
|
|||||||
@clear-preferences-and-logout="clearPreferencesAndLogout"
|
@clear-preferences-and-logout="clearPreferencesAndLogout"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<EasyFlowBackTop />
|
<EasyFlowBackTop
|
||||||
|
:bottom-label="$t('button.goToBottom')"
|
||||||
|
is-group
|
||||||
|
:top-label="$t('button.backToTop')"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</EasyFlowAdminLayout>
|
</EasyFlowAdminLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user