mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-21 17:21:09 +01:00
feat: add display configuration options and enhance element filtering in AutoProbeResultsPreview component
- Introduced showLabel and focusMode options for better control over element display. - Updated page element filtering logic to accommodate new focus mode functionality. - Added translations for new display configuration options in English and Chinese. - Included csstype dependency for improved type management in styles.
This commit is contained in:
@@ -169,6 +169,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/semver": "^7.5.8",
|
||||
"csstype": "^3.1.3",
|
||||
"json-editor-vue": "^0.17.0",
|
||||
"semver": "^7.6.3",
|
||||
"uuid": "^10.0.0"
|
||||
|
||||
3
frontend/crawlab-ui/pnpm-lock.yaml
generated
3
frontend/crawlab-ui/pnpm-lock.yaml
generated
@@ -101,6 +101,9 @@ importers:
|
||||
cronstrue:
|
||||
specifier: ^2.50.0
|
||||
version: 2.59.0
|
||||
csstype:
|
||||
specifier: ^3.1.3
|
||||
version: 3.1.3
|
||||
dayjs:
|
||||
specifier: ^1.11.11
|
||||
version: 1.11.13
|
||||
|
||||
@@ -8,8 +8,9 @@ import {
|
||||
onBeforeUnmount,
|
||||
} from 'vue';
|
||||
import useRequest from '@/services/request';
|
||||
import { getIconByPageElementType } from '@/utils';
|
||||
import { getIconByPageElementType, translate } from '@/utils';
|
||||
import { debounce } from 'lodash';
|
||||
import type { Property } from 'csstype';
|
||||
|
||||
const props = defineProps<{
|
||||
activeId: string;
|
||||
@@ -17,6 +18,8 @@ const props = defineProps<{
|
||||
viewport?: PageViewPort;
|
||||
}>();
|
||||
|
||||
const t = translate;
|
||||
|
||||
const { get } = useRequest();
|
||||
|
||||
const previewRef = ref<HTMLDivElement | null>(null);
|
||||
@@ -32,6 +35,11 @@ const updateOverlayScale = () => {
|
||||
screenshotScale.value = rect.width / (viewport?.width ?? 1280);
|
||||
};
|
||||
|
||||
const displayConfig = ref({
|
||||
showLabel: true,
|
||||
focusMode: true,
|
||||
});
|
||||
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
@@ -105,33 +113,98 @@ const getElementMaskStyle = (el: PageElement): CSSProperties => {
|
||||
|
||||
const pageElements = computed<PageElement[]>(() => {
|
||||
const { activeNavItem } = props;
|
||||
if (!activeNavItem) {
|
||||
return previewResult.value?.page_elements ?? [];
|
||||
}
|
||||
const { focusMode } = displayConfig.value;
|
||||
if (!previewResult.value?.page_elements) {
|
||||
return [];
|
||||
}
|
||||
if (activeNavItem.type === 'page_pattern') {
|
||||
return previewResult.value.page_elements;
|
||||
} else if (activeNavItem.type === 'list') {
|
||||
const listElement = previewResult.value.page_elements.find(
|
||||
el => el.name === activeNavItem.name
|
||||
const fieldElements = previewResult.value.page_elements.filter(
|
||||
el => el.type === 'field'
|
||||
);
|
||||
const listElements = previewResult.value.page_elements.filter(
|
||||
el => el.type === 'list'
|
||||
);
|
||||
|
||||
// If focus mode is not enabled, return all elements
|
||||
if (!focusMode) {
|
||||
const allListItemElements = listElements.flatMap(el => el.children || []);
|
||||
const allListFieldElements = allListItemElements.flatMap(
|
||||
el => el.children || []
|
||||
);
|
||||
if (!listElement) {
|
||||
return [];
|
||||
}
|
||||
const itemElements = listElement.children ?? [];
|
||||
const fieldElements = itemElements.flatMap(el => el.children ?? []);
|
||||
return [...itemElements, ...fieldElements];
|
||||
} else if (activeNavItem.type === 'field') {
|
||||
return (
|
||||
previewResult.value.page_elements.filter(
|
||||
el => el.name === activeNavItem.name
|
||||
) ?? []
|
||||
);
|
||||
} else {
|
||||
return [
|
||||
...fieldElements,
|
||||
...listElements,
|
||||
...allListItemElements,
|
||||
...allListFieldElements,
|
||||
];
|
||||
}
|
||||
|
||||
// If focus mode is enabled, filter elements based on the active navigation item
|
||||
if (!activeNavItem) {
|
||||
return [];
|
||||
}
|
||||
switch (activeNavItem.type) {
|
||||
case 'page_pattern':
|
||||
return [...fieldElements, ...listElements];
|
||||
case 'list':
|
||||
const listElement = listElements.find(
|
||||
el => el.name === activeNavItem.name
|
||||
);
|
||||
if (!listElement) {
|
||||
return [];
|
||||
}
|
||||
const listItemElements = listElements.flatMap(el => el.children || []);
|
||||
const listFieldElements = listItemElements.flatMap(
|
||||
el => el.children || []
|
||||
);
|
||||
return [
|
||||
{ ...listElement, active: true },
|
||||
...listItemElements,
|
||||
...listFieldElements,
|
||||
];
|
||||
case 'field':
|
||||
// Non-list field
|
||||
if (activeNavItem.parent?.type === 'page_pattern') {
|
||||
const fieldElement = fieldElements.find(
|
||||
el => el.name === activeNavItem.name
|
||||
);
|
||||
if (!fieldElement) {
|
||||
return [];
|
||||
}
|
||||
return [{ ...fieldElement, active: true }];
|
||||
}
|
||||
// List item field
|
||||
if (activeNavItem.parent?.type === 'list') {
|
||||
const listElement = listElements.find(
|
||||
el => el.name === activeNavItem.parent!.name
|
||||
);
|
||||
if (!listElement) {
|
||||
return [];
|
||||
}
|
||||
const listItemElements = listElements.flatMap(el => el.children || []);
|
||||
const listFieldElements = listItemElements
|
||||
.flatMap(el => el.children || [])
|
||||
.filter(el => el.name === activeNavItem.name)
|
||||
.map(el => ({ ...el, active: true }));
|
||||
return [...listItemElements, ...listFieldElements];
|
||||
}
|
||||
return [];
|
||||
case 'pagination':
|
||||
const paginationElement = previewResult.value.page_elements.find(
|
||||
el => el.type === 'pagination' && el.name === activeNavItem.name
|
||||
);
|
||||
if (!paginationElement) {
|
||||
return [];
|
||||
}
|
||||
return [{ ...paginationElement, active: true }];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
const viewportDisplay = computed(() => {
|
||||
const { viewport } = props;
|
||||
if (!viewport) return '';
|
||||
return `${viewport.width}x${viewport.height}`;
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
@@ -143,16 +216,38 @@ defineOptions({ name: 'ClAutoProbeResultsPreview' });
|
||||
|
||||
<template>
|
||||
<div ref="previewRef" class="preview">
|
||||
<div class="preview-control">
|
||||
<cl-tag :icon="['fa', 'desktop']" :label="viewportDisplay" />
|
||||
<el-checkbox
|
||||
v-model="displayConfig.showLabel"
|
||||
:label="t('components.autoprobe.pagePattern.displayConfig.showLabel')"
|
||||
/>
|
||||
<el-checkbox
|
||||
v-model="displayConfig.focusMode"
|
||||
:label="t('components.autoprobe.pagePattern.displayConfig.focusMode')"
|
||||
/>
|
||||
</div>
|
||||
<div v-loading="previewLoading" class="preview-container">
|
||||
<div v-if="previewResult" class="preview-overlay">
|
||||
<img ref="screenshotRef" class="screenshot" :src="previewResult.screenshot_base64" />
|
||||
<img
|
||||
ref="screenshotRef"
|
||||
class="screenshot"
|
||||
:src="previewResult.screenshot_base64"
|
||||
/>
|
||||
<div
|
||||
v-for="(el, index) in pageElements"
|
||||
:key="index"
|
||||
class="element-mask"
|
||||
:class="el.active ? 'active' : ''"
|
||||
:style="getElementMaskStyle(el)"
|
||||
>
|
||||
<el-badge type="primary" :badge-style="{ opacity: 0.8 }">
|
||||
<el-badge
|
||||
:type="el.active ? 'danger' : 'primary'"
|
||||
:hidden="!displayConfig.showLabel"
|
||||
:badge-style="{
|
||||
opacity: el.active ? 1 : 0.5,
|
||||
}"
|
||||
>
|
||||
<template #content>
|
||||
<span style="margin-right: 5px">
|
||||
<cl-icon :icon="getIconByPageElementType(el.type)" />
|
||||
@@ -171,10 +266,23 @@ defineOptions({ name: 'ClAutoProbeResultsPreview' });
|
||||
overflow: hidden;
|
||||
height: calc(100% - 41px);
|
||||
|
||||
.preview-control {
|
||||
height: 40px;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 8px;
|
||||
|
||||
.el-checkbox {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: calc(100% - 40px);
|
||||
overflow: auto;
|
||||
scrollbar-width: none;
|
||||
|
||||
@@ -194,10 +302,16 @@ defineOptions({ name: 'ClAutoProbeResultsPreview' });
|
||||
border: 1px solid var(--el-color-primary-light-5);
|
||||
border-radius: 4px;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: rgba(64, 156, 255, 0.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.active {
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
border: 3px solid var(--el-color-danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,17 @@ const autoprobe: LComponentsAutoProbe = {
|
||||
html: 'HTML',
|
||||
},
|
||||
attribute: 'Attribute',
|
||||
elementType: 'Element Type',
|
||||
elementTypes: {
|
||||
list: 'List',
|
||||
listItem: 'List Item',
|
||||
field: 'Field',
|
||||
pagination: 'Pagination',
|
||||
},
|
||||
displayConfig: {
|
||||
showLabel: 'Show Label',
|
||||
focusMode: 'Focus Mode',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -114,6 +114,17 @@ const autoprobe: LComponentsAutoProbe = {
|
||||
attribute: '属性',
|
||||
},
|
||||
attribute: '属性',
|
||||
elementType: '元素类型',
|
||||
elementTypes: {
|
||||
list: '列表',
|
||||
listItem: '列表项',
|
||||
field: '字段',
|
||||
pagination: '分页',
|
||||
},
|
||||
displayConfig: {
|
||||
showLabel: '显示标签',
|
||||
focusMode: '聚焦模式',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -114,5 +114,16 @@ interface LComponentsAutoProbe {
|
||||
html: string;
|
||||
};
|
||||
attribute: string;
|
||||
elementType: string;
|
||||
elementTypes: {
|
||||
list: string;
|
||||
listItem: string;
|
||||
field: string;
|
||||
pagination: string;
|
||||
};
|
||||
displayConfig: {
|
||||
showLabel: string;
|
||||
focusMode: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ export declare global {
|
||||
url?: string;
|
||||
query?: string;
|
||||
last_task_id?: string;
|
||||
last_task?: AutoProbeTask;
|
||||
last_task_status?: AutoProbeTaskStatus;
|
||||
last_task_error?: string;
|
||||
default_task_id?: string;
|
||||
run_on_create?: boolean;
|
||||
page_pattern?: PagePattern;
|
||||
@@ -118,6 +119,7 @@ export declare global {
|
||||
type: PageElementType;
|
||||
coordinates: ElementCoordinates;
|
||||
children?: PageElement[];
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
interface PagePreviewResult {
|
||||
|
||||
@@ -113,7 +113,8 @@ const useAutoProbeList = () => {
|
||||
icon: ['fa', 'heartbeat'],
|
||||
width: '120',
|
||||
value: (row: AutoProbe) => {
|
||||
const { status, error } = row.last_task || {};
|
||||
const status = row.last_task_status;
|
||||
const error = row.last_task_error;
|
||||
if (!status) return;
|
||||
return (
|
||||
<ClAutoProbeTaskStatus
|
||||
@@ -191,8 +192,13 @@ const useAutoProbeList = () => {
|
||||
);
|
||||
|
||||
const rowKey = (row: AutoProbe) => {
|
||||
return JSON.stringify([row._id, row.url, row.last_task?.status, row.page_pattern]);
|
||||
}
|
||||
return JSON.stringify([
|
||||
row._id,
|
||||
row.url,
|
||||
row.last_task?.status,
|
||||
row.page_pattern,
|
||||
]);
|
||||
};
|
||||
|
||||
setupAutoUpdate(getList);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user