feat: implement preview functionality in AutoProbeResultsContainer with enhanced state management and new interfaces

- Added preview feature to display page screenshots and item coordinates.
- Introduced new interfaces for viewport and element coordinates in autoprobe models.
- Improved iframe loading state management and dynamic styling for preview elements.
- Updated AutoProbeDetailTabPatterns to pass activeId prop for better data handling.
This commit is contained in:
Marvin Zhang
2025-05-16 23:38:13 +08:00
parent c0b0cc9f7e
commit 07a509c198
3 changed files with 123 additions and 25 deletions

View File

@@ -1,9 +1,10 @@
<script setup lang="tsx">
import { computed, ref } from 'vue';
import { CellStyle } from 'element-plus';
import { ClTag } from '@/components';
import { translate, getIconByItemType } from '@/utils';
import { TAB_NAME_RESULTS, TAB_NAME_PREVIEW } from '@/constants';
import { CellStyle } from 'element-plus';
import useRequest from '@/services/request';
const t = translate;
@@ -13,12 +14,25 @@ const props = defineProps<{
fields?: AutoProbeNavItem[];
activeFieldName?: string;
url?: string;
activeId?: string;
}>();
// Emits
const emit = defineEmits<{
(e: 'size-change', size: number): void;
}>();
const { post } = useRequest();
// Refs
const resultsContainerRef = ref<HTMLElement | null>(null);
const iframeRef = ref<HTMLIFrameElement | null>(null);
const iframeLoading = ref(true);
const previewRef = ref<HTMLDivElement | null>(null);
const previewLoading = ref(false);
const pagePreview = ref<PagePreview>();
const overlayRef = ref<HTMLDivElement | null>(null);
const scrollPosition = ref({ top: 0, left: 0 });
// States
const activeTabName = ref<string | undefined>(TAB_NAME_RESULTS);
@@ -80,7 +94,7 @@ const tableCellStyle: CellStyle<PageData> = ({ column }) => {
};
// Methods
const onTabSelect = (id: string) => {
const onTabSelect = async (id: string) => {
activeTabName.value = id;
if (!resultsVisible.value) {
resultsVisible.value = true;
@@ -89,6 +103,7 @@ const onTabSelect = (id: string) => {
// Reset iframe loading state when switching to preview tab
if (id === TAB_NAME_PREVIEW) {
iframeLoading.value = true;
setTimeout(getPreview, 10);
}
};
@@ -101,6 +116,32 @@ const toggleResults = () => {
const onIframeLoad = () => {
iframeLoading.value = false;
iframeRef.value?.addEventListener('focus', event => {
console.debug(event);
});
};
const getPreview = async () => {
const { activeId } = props;
const rect = previewRef.value?.getBoundingClientRect();
const viewport: PageViewPort | undefined = rect
? {
width: rect.width,
height: rect.height,
}
: undefined;
previewLoading.value = true;
try {
const res = await post<any, ResponseWithData<PagePreview>>(
`/ai/autoprobes/${activeId}/preview`,
{
viewport,
}
);
pagePreview.value = res.data;
} finally {
previewLoading.value = false;
}
};
// Resize handler
@@ -110,11 +151,6 @@ const onSizeChange = (size: number) => {
emit('size-change', size);
};
// Emits
const emit = defineEmits<{
(e: 'size-change', size: number): void;
}>();
defineOptions({ name: 'ClAutoProbeResultsContainer' });
</script>
@@ -162,16 +198,40 @@ defineOptions({ name: 'ClAutoProbeResultsContainer' });
hide-footer
/>
</div>
<div class="preview" v-else-if="activeTabName === TAB_NAME_PREVIEW">
<el-skeleton :rows="15" animated v-if="iframeLoading && url" />
<div class="iframe-container">
<iframe
v-if="url"
ref="iframeRef"
:src="url"
sandbox="allow-scripts allow-same-origin"
@load="onIframeLoad"
/>
<div
v-else-if="activeTabName === TAB_NAME_PREVIEW"
ref="previewRef"
class="preview"
>
<!-- <el-skeleton :rows="15" animated v-if="iframeLoading && url" />-->
<div v-loading="previewLoading" class="preview-container">
<div v-if="pagePreview" ref="overlayRef" class="preview-overlay">
<img class="screenshot" :src="pagePreview.screenshot_base64" />
<div
v-for="coord in pagePreview.page_items_coordinates"
:key="coord.id"
class="element-mask"
:style="{
position: 'absolute',
left: coord.coordinates?.left + 'px',
top: coord.coordinates?.top + 'px',
width: coord.coordinates?.width + 'px',
height: coord.coordinates?.height + 'px',
}"
>
<el-badge
type="primary"
:badge-style="{opacity: 0.5}"
>
<template #content>
<span style="margin-right: 5px">
<cl-icon :icon="getIconByItemType('field')" />
</span>
{{ coord.id }}
</template>
</el-badge>
</div>
</div>
</div>
</div>
</div>
@@ -236,19 +296,33 @@ defineOptions({ name: 'ClAutoProbeResultsContainer' });
overflow: hidden;
height: calc(100% - 41px);
.el-skeleton {
padding: 20px;
}
.iframe-container {
.preview-container {
position: relative;
width: 100%;
height: 100%;
overflow: auto;
scrollbar-width: none;
iframe {
.preview-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
img.screenshot {
width: 100%;
}
.element-mask {
border: 1px solid var(--el-color-primary);
border-radius: 4px;
pointer-events: none;
&:hover {
background-color: var(--cl-primary-color);
opacity: 0.8;
}
}
}
}
}

View File

@@ -86,4 +86,27 @@ export declare global {
fields?: AutoProbeNavItem[];
activeField?: AutoProbeNavItem;
}
interface PageViewPort {
width: number;
height: number;
}
interface ElementCoordinates {
top: number;
left: number;
width: number;
height: number;
}
interface PageItemCoordinates {
id: string;
name: string;
coordinates: ElementCoordinates;
}
interface PagePreview {
screenshot_base64: string;
page_items_coordinates: PageItemCoordinates[];
}
}

View File

@@ -264,6 +264,7 @@ defineOptions({ name: 'ClAutoProbeDetailTabPatterns' });
:fields="resultsFields"
:active-field-name="resultsActiveField?.name"
:url="form.url"
:active-id="activeId"
@size-change="onSizeChange"
/>
</div>