mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-26 17:49:15 +01:00
feat: update AutoProbe components and enhance functionality
- Removed AutoProbeFieldRule component and integrated AutoProbeSelector for improved selector management. - Updated AutoProbePagePatternDetail to utilize new table structure for better data representation. - Enhanced internationalization support with new translations for selector types and field counts. - Refactored AutoProbeDetailTabPatterns to streamline data handling and improve user experience.
This commit is contained in:
@@ -1,53 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
fieldRule: FieldRule;
|
||||
}>();
|
||||
|
||||
defineOptions({ name: 'ClAutoProbeFieldRule' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="field-rule">
|
||||
<div class="name">
|
||||
<span class="icon">
|
||||
<cl-icon :icon="['fa', 'share-nodes']" />
|
||||
</span>
|
||||
<label>{{ fieldRule.name }}</label>
|
||||
</div>
|
||||
<div class="selector">
|
||||
<span>{{ fieldRule.selector_type }}</span>
|
||||
<span>{{ fieldRule.selector }}</span>
|
||||
</div>
|
||||
<div class="extraction">
|
||||
<span>{{ fieldRule.extraction_type }}</span>
|
||||
<span>{{ fieldRule.attribute_name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.field-rule {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.name {
|
||||
flex: 0 0 180px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.extraction {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,120 +1,125 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="tsx">
|
||||
import { computed } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import {
|
||||
ClNavLink,
|
||||
ClIcon,
|
||||
ClAutoProbeSelector,
|
||||
} from '@/components';
|
||||
import { translate } from '@/utils';
|
||||
|
||||
// i18n
|
||||
const t = translate;
|
||||
|
||||
// props
|
||||
const props = defineProps<{
|
||||
pagePattern: AutoProbeNavItem;
|
||||
pageData?: any;
|
||||
}>();
|
||||
|
||||
const t = translate;
|
||||
|
||||
// store
|
||||
const store = useStore();
|
||||
const { autoprobe: state } = store.state as RootStoreState;
|
||||
const form = computed<AutoProbe>(() => state.form);
|
||||
|
||||
// Page pattern data
|
||||
const pagePatternData = computed(() => form.value?.page_pattern);
|
||||
|
||||
// Stats
|
||||
const fieldCount = computed(() => pagePatternData.value?.fields?.length || 0);
|
||||
const listCount = computed(() => pagePatternData.value?.lists?.length || 0);
|
||||
const hasPagination = computed(() => !!pagePatternData.value?.pagination);
|
||||
|
||||
// Check if we have top-level properties to display as tables
|
||||
const topLevelArrays = computed(() => {
|
||||
if (!props.pageData || typeof props.pageData !== 'object') return [];
|
||||
|
||||
const result = [];
|
||||
for (const key in props.pageData) {
|
||||
if (Array.isArray(props.pageData[key]) && props.pageData[key].length > 0) {
|
||||
// Only include arrays that have objects inside (suitable for tables)
|
||||
if (typeof props.pageData[key][0] === 'object' && props.pageData[key][0] !== null) {
|
||||
result.push({
|
||||
key,
|
||||
data: props.pageData[key],
|
||||
columns: getColumnsFromData(props.pageData[key])
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
// Helper function to extract columns from data
|
||||
function getColumnsFromData(data: any[]): {prop: string; label: string}[] {
|
||||
if (!data || !data.length) return [];
|
||||
const firstItem = data[0];
|
||||
if (typeof firstItem !== 'object' || firstItem === null) return [];
|
||||
|
||||
return Object.keys(firstItem).map(key => ({
|
||||
prop: key,
|
||||
label: key.charAt(0).toUpperCase() + key.slice(1) // Capitalize first letter
|
||||
}));
|
||||
}
|
||||
|
||||
// Format page data for display (for properties that aren't array tables)
|
||||
const formattedPageData = computed(() => {
|
||||
if (!props.pageData) return [];
|
||||
|
||||
// Convert the page data to a format suitable for display
|
||||
const result = [];
|
||||
const arrayKeys = topLevelArrays.value.map(item => item.key);
|
||||
|
||||
// If it's an object, display key-value pairs (excluding array tables)
|
||||
if (typeof props.pageData === 'object' && !Array.isArray(props.pageData)) {
|
||||
for (const key in props.pageData) {
|
||||
// Skip keys that are already displayed as tables
|
||||
if (arrayKeys.includes(key)) continue;
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(props.pageData, key)) {
|
||||
const value = props.pageData[key];
|
||||
let displayValue = value;
|
||||
|
||||
// Format based on value type
|
||||
if (typeof value === 'object') {
|
||||
try {
|
||||
displayValue = JSON.stringify(value, null, 2);
|
||||
} catch (e) {
|
||||
displayValue = String(value);
|
||||
}
|
||||
const tableColumns = computed<TableColumns<AutoProbeNavItem>>(() => {
|
||||
return [
|
||||
{
|
||||
key: 'name',
|
||||
label: t('components.autoprobe.pagePattern.name'),
|
||||
width: '200',
|
||||
value: (row: AutoProbeNavItem) => {
|
||||
let icon: Icon;
|
||||
switch (row.type) {
|
||||
case 'field':
|
||||
icon = ['fa', 'tag'];
|
||||
break;
|
||||
case 'list':
|
||||
icon = ['fa', 'list'];
|
||||
break;
|
||||
case 'pagination':
|
||||
icon = ['fa', 'ellipsis-h'];
|
||||
break;
|
||||
default:
|
||||
icon = ['fa', 'question'];
|
||||
}
|
||||
|
||||
result.push({
|
||||
key,
|
||||
value: displayValue
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// If it's an array but not suitable for tables
|
||||
else if (Array.isArray(props.pageData)) {
|
||||
try {
|
||||
result.push({
|
||||
key: 'array',
|
||||
value: JSON.stringify(props.pageData, null, 2)
|
||||
});
|
||||
} catch (e) {
|
||||
result.push({
|
||||
key: 'array',
|
||||
value: String(props.pageData)
|
||||
});
|
||||
}
|
||||
return (
|
||||
<ClNavLink onClick={() => {}}>
|
||||
<span style={{ marginRight: '5px' }}>
|
||||
<ClIcon icon={icon} />
|
||||
</span>
|
||||
{row.label}
|
||||
</ClNavLink>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
label: t('components.autoprobe.pagePattern.type'),
|
||||
width: '100',
|
||||
value: (row: AutoProbeNavItem) => {
|
||||
return t(`components.autoprobe.pagePattern.types.${row.type}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'fields',
|
||||
label: t('components.autoprobe.pagePattern.fieldCount'),
|
||||
width: '80',
|
||||
value: (row: AutoProbeNavItem) => {
|
||||
if (row.type === 'list') {
|
||||
return <ClNavLink label={row.fieldCount} onClick={() => {}} />;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'selector',
|
||||
label: t('components.autoprobe.pagePattern.selector'),
|
||||
width: 'auto',
|
||||
minWidth: '300',
|
||||
value: (row: AutoProbeNavItem) => {
|
||||
switch (row.type) {
|
||||
case 'field':
|
||||
return <ClAutoProbeSelector type="field" rule={row.field} />;
|
||||
case 'pagination':
|
||||
return <ClAutoProbeSelector type="pagination" rule={row.pagination} />;
|
||||
}
|
||||
},
|
||||
},
|
||||
] as TableColumns<AutoProbeNavItem>;
|
||||
});
|
||||
const tableData = computed<TableData<AutoProbeNavItem>>(() => {
|
||||
const { pagePattern } = props;
|
||||
if (!pagePattern?.children?.length) {
|
||||
return [];
|
||||
}
|
||||
// For primitive types
|
||||
else {
|
||||
result.push({
|
||||
key: 'value',
|
||||
value: props.pageData
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
return pagePattern.children.map((item: AutoProbeNavItem) => {
|
||||
switch (item.type) {
|
||||
case 'list':
|
||||
return {
|
||||
name: item.name,
|
||||
label: item.name,
|
||||
type: item.type,
|
||||
fieldCount: item.children?.length || 0,
|
||||
};
|
||||
case 'field':
|
||||
return {
|
||||
name: item.name,
|
||||
label: item.name,
|
||||
type: item.type,
|
||||
field: item.field,
|
||||
};
|
||||
case 'pagination':
|
||||
return {
|
||||
name: item.name,
|
||||
label: item.name,
|
||||
type: item.type,
|
||||
pagination: item.pagination,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
name: item.name,
|
||||
label: item.name,
|
||||
type: item.type,
|
||||
};
|
||||
}
|
||||
}) as TableData<AutoProbeNavItem>;
|
||||
});
|
||||
|
||||
defineOptions({ name: 'ClAutoProbePagePatternDetail' });
|
||||
@@ -122,186 +127,94 @@ defineOptions({ name: 'ClAutoProbePagePatternDetail' });
|
||||
|
||||
<template>
|
||||
<div class="cl-autoprobe-page-pattern-detail">
|
||||
<div class="header">
|
||||
<h3>{{ t('components.autoprobe.pagePattern.title') }}</h3>
|
||||
</div>
|
||||
|
||||
<div v-if="pagePatternData" class="content">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item :label="t('components.autoprobe.pagePattern.name')">
|
||||
{{ pagePatternData.name }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="stats-section">
|
||||
<h4>{{ t('components.autoprobe.pagePattern.stats') }}</h4>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ fieldCount }}</div>
|
||||
<div class="stat-label">{{ t('components.autoprobe.pagePattern.fields') }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ listCount }}</div>
|
||||
<div class="stat-label">{{ t('components.autoprobe.pagePattern.lists') }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ hasPagination ? '✓' : '✗' }}</div>
|
||||
<div class="stat-label">{{ t('components.autoprobe.pagePattern.hasPagination') }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- Array Tables Section - For data that can be displayed as tables -->
|
||||
<template v-if="topLevelArrays.length">
|
||||
<div v-for="(arrayInfo, index) in topLevelArrays" :key="index" class="page-data-section">
|
||||
<h4>{{ arrayInfo.key }}</h4>
|
||||
<div class="table">
|
||||
<el-table
|
||||
:data="arrayInfo.data"
|
||||
border
|
||||
style="width: 100%"
|
||||
height="400"
|
||||
highlight-current-row
|
||||
>
|
||||
<el-table-column
|
||||
v-for="column in arrayInfo.columns"
|
||||
:key="column.prop"
|
||||
:prop="column.prop"
|
||||
:label="column.label"
|
||||
sortable
|
||||
>
|
||||
<template #default="scope">
|
||||
<template v-if="typeof scope.row[column.prop] !== 'object' || scope.row[column.prop] === null">
|
||||
{{ scope.row[column.prop] }}
|
||||
</template>
|
||||
<pre v-else class="json-value">{{ JSON.stringify(scope.row[column.prop], null, 2) }}</pre>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="table-footer">
|
||||
<span class="total-items">
|
||||
{{ `Total: ${arrayInfo.data.length} ${arrayInfo.data.length === 1 ? 'item' : 'items'}` }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Other Page Data Section - For non-tabular data -->
|
||||
<div v-if="formattedPageData.length" class="page-data-section">
|
||||
<h4>{{ t('components.autoprobe.pagePattern.otherProperties') || 'Other Properties' }}</h4>
|
||||
<el-table :data="formattedPageData" border style="width: 100%">
|
||||
<el-table-column prop="key" :label="t('components.autoprobe.pageData.key') || 'Key'" width="200" />
|
||||
<el-table-column :label="t('components.autoprobe.pageData.value') || 'Value'">
|
||||
<template #default="scope">
|
||||
<pre v-if="typeof scope.row.value === 'string' && (scope.row.value.startsWith('{') || scope.row.value.startsWith('['))" class="json-value">{{ scope.row.value }}</pre>
|
||||
<span v-else>{{ scope.row.value }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="not-found">
|
||||
{{ t('components.autoprobe.pagePattern.notFound') || 'Page pattern details not found' }}
|
||||
</div>
|
||||
<cl-table :columns="tableColumns" :data="tableData" embedded hide-footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.cl-autoprobe-page-pattern-detail {
|
||||
padding: 16px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
|
||||
.header {
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
|
||||
|
||||
.stats-section {
|
||||
margin-top: 20px;
|
||||
|
||||
|
||||
h4 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
.stat-card {
|
||||
background-color: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
|
||||
.stat-label {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.page-data-section {
|
||||
margin-top: 20px;
|
||||
|
||||
|
||||
h4 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
|
||||
|
||||
.el-table__inner-wrapper {
|
||||
position: relative;
|
||||
overflow: unset;
|
||||
}
|
||||
|
||||
|
||||
.el-table__header-wrapper {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
.table-footer {
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
|
||||
|
||||
.total-items {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.json-value {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
@@ -312,7 +225,7 @@ defineOptions({ name: 'ClAutoProbePagePatternDetail' });
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.not-found {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-style: italic;
|
||||
@@ -320,4 +233,4 @@ defineOptions({ name: 'ClAutoProbePagePatternDetail' });
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -121,7 +121,7 @@ defineOptions({ name: 'ClAutoProbePatternStats' });
|
||||
<div v-else class="autoprobe-stats-empty">
|
||||
<cl-tag
|
||||
:icon="['fa', 'question-circle']"
|
||||
label="No Pattern"
|
||||
:label="t('common.status.unknown')"
|
||||
type="info"
|
||||
:clickable="clickable"
|
||||
@click="emit('click')"
|
||||
@@ -137,6 +137,5 @@ defineOptions({ name: 'ClAutoProbePatternStats' });
|
||||
|
||||
.autoprobe-stats-empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { translate } from '@/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
type: 'field' | 'pagination';
|
||||
rule: FieldRule | Pagination;
|
||||
}>();
|
||||
|
||||
const t = translate;
|
||||
|
||||
const selectorIcon = computed<Icon>(() => {
|
||||
const { rule } = props;
|
||||
switch (rule.selector_type) {
|
||||
case 'css':
|
||||
return ['fab', 'css'];
|
||||
case 'xpath':
|
||||
return ['fa', 'code'];
|
||||
case 'regex':
|
||||
return ['fa', 'search'];
|
||||
}
|
||||
});
|
||||
|
||||
const selectorType = computed(() => {});
|
||||
|
||||
const extractionIcon = computed<Icon>(() => {
|
||||
const { rule, type } = props;
|
||||
if (type === 'field') {
|
||||
switch ((rule as FieldRule).extraction_type) {
|
||||
case 'attribute':
|
||||
return ['fa', 'tag'];
|
||||
case 'text':
|
||||
return ['fa', 'font'];
|
||||
case 'html':
|
||||
return ['fa', 'code'];
|
||||
default:
|
||||
return ['fa', 'question'];
|
||||
}
|
||||
} else {
|
||||
return ['fa', 'question'];
|
||||
}
|
||||
});
|
||||
|
||||
const extractionLabel = computed(() => {
|
||||
const { rule, type } = props;
|
||||
if (type === 'field') {
|
||||
return (rule as FieldRule).extraction_type;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
defineOptions({ name: 'ClAutoProbeSelector' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="selector">
|
||||
<cl-tag
|
||||
:icon="selectorIcon"
|
||||
:label="rule.selector"
|
||||
:tooltip="
|
||||
t(
|
||||
`components.autoprobe.pagePattern.selectorTypes.${rule.selector_type}`
|
||||
)
|
||||
"
|
||||
/>
|
||||
<template v-if="type === 'field'">
|
||||
<span class="divider">
|
||||
<cl-icon :icon="['fa', 'angle-right']" />
|
||||
</span>
|
||||
<cl-tag :icon="extractionIcon" :label="extractionLabel" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
|
||||
.divider {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,12 +20,12 @@ import * as VariableNode from './ui/lexical/nodes/VariableNode';
|
||||
import AssistantConsole from './core/ai/AssistantConsole.vue';
|
||||
import AtomMaterialIcon from './ui/icon/AtomMaterialIcon.vue';
|
||||
import AutoProbeFieldDetail from './core/autoprobe/AutoProbeFieldDetail.vue';
|
||||
import AutoProbeFieldRule from './core/autoprobe/AutoProbeFieldRule.vue';
|
||||
import AutoProbeForm from './core/autoprobe/AutoProbeForm.vue';
|
||||
import AutoProbeListDetail from './core/autoprobe/AutoProbeListDetail.vue';
|
||||
import AutoProbePagePatternDetail from './core/autoprobe/AutoProbePagePatternDetail.vue';
|
||||
import AutoProbePaginationDetail from './core/autoprobe/AutoProbePaginationDetail.vue';
|
||||
import AutoProbePatternStats from './core/autoprobe/AutoProbePatternStats.vue';
|
||||
import AutoProbeSelector from './core/autoprobe/AutoProbeSelector.vue';
|
||||
import AutoProbeTaskStatus from './core/autoprobe/AutoProbeTaskStatus.vue';
|
||||
import BlockOptionsDropdownList from './ui/lexical/components/BlockOptionsDropdownList.vue';
|
||||
import Box from './ui/box/Box.vue';
|
||||
@@ -276,12 +276,12 @@ export {
|
||||
AssistantConsole as ClAssistantConsole,
|
||||
AtomMaterialIcon as ClAtomMaterialIcon,
|
||||
AutoProbeFieldDetail as ClAutoProbeFieldDetail,
|
||||
AutoProbeFieldRule as ClAutoProbeFieldRule,
|
||||
AutoProbeForm as ClAutoProbeForm,
|
||||
AutoProbeListDetail as ClAutoProbeListDetail,
|
||||
AutoProbePagePatternDetail as ClAutoProbePagePatternDetail,
|
||||
AutoProbePaginationDetail as ClAutoProbePaginationDetail,
|
||||
AutoProbePatternStats as ClAutoProbePatternStats,
|
||||
AutoProbeSelector as ClAutoProbeSelector,
|
||||
AutoProbeTaskStatus as ClAutoProbeTaskStatus,
|
||||
BlockOptionsDropdownList as ClBlockOptionsDropdownList,
|
||||
Box as ClBox,
|
||||
|
||||
@@ -74,12 +74,26 @@ const autoprobe: LComponentsAutoProbe = {
|
||||
},
|
||||
pagePattern: {
|
||||
title: 'Page Pattern',
|
||||
type: 'Type',
|
||||
name: 'Name',
|
||||
stats: 'Statistics',
|
||||
fields: 'Fields',
|
||||
lists: 'Lists',
|
||||
hasPagination: 'Has Pagination',
|
||||
notFound: 'Page pattern details not found',
|
||||
fieldCount: 'Fields',
|
||||
types: {
|
||||
field: 'Field',
|
||||
list: 'List',
|
||||
pagination: 'Pagination',
|
||||
},
|
||||
selector: 'Selector',
|
||||
selectorType: 'Selector Type',
|
||||
selectorTypes: {
|
||||
css: 'CSS',
|
||||
xpath: 'XPath',
|
||||
regex: 'Regex',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -74,12 +74,26 @@ const autoprobe: LComponentsAutoProbe = {
|
||||
},
|
||||
pagePattern: {
|
||||
title: '页面模式',
|
||||
type: '类型',
|
||||
name: '名称',
|
||||
stats: '统计',
|
||||
fields: '字段',
|
||||
lists: '列表',
|
||||
hasPagination: '有分页',
|
||||
notFound: '未找到页面模式详情',
|
||||
fieldCount: '字段数',
|
||||
types: {
|
||||
field: '字段',
|
||||
list: '列表',
|
||||
pagination: '分页',
|
||||
},
|
||||
selector: '选择器',
|
||||
selectorType: '选择器类型',
|
||||
selectorTypes: {
|
||||
css: 'CSS',
|
||||
xpath: 'XPath',
|
||||
regex: '正则表达式',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -74,11 +74,25 @@ interface LComponentsAutoProbe {
|
||||
};
|
||||
pagePattern: {
|
||||
title: string;
|
||||
type: string;
|
||||
name: string;
|
||||
stats: string;
|
||||
fields: string;
|
||||
lists: string;
|
||||
hasPagination: string;
|
||||
notFound: string;
|
||||
fieldCount: string;
|
||||
types: {
|
||||
field: string;
|
||||
list: string;
|
||||
pagination: string;
|
||||
};
|
||||
selector: string;
|
||||
selectorType: string;
|
||||
selectorTypes: {
|
||||
css: string;
|
||||
xpath: string;
|
||||
regex: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,12 +19,14 @@ export declare global {
|
||||
|
||||
type SelectorType = 'css' | 'xpath' | 'regex';
|
||||
type ExtractType = 'text' | 'attribute' | 'html';
|
||||
type PaginationType = 'next' | 'load' | 'scroll';
|
||||
|
||||
interface FieldRule {
|
||||
interface BaseSelector {
|
||||
name: string;
|
||||
selector_type: SelectorType;
|
||||
selector: string;
|
||||
}
|
||||
|
||||
interface FieldRule extends BaseSelector {
|
||||
extraction_type: ExtractType;
|
||||
attribute_name?: string;
|
||||
default_value?: string;
|
||||
@@ -44,13 +46,7 @@ export declare global {
|
||||
item_pattern: ItemPattern;
|
||||
}
|
||||
|
||||
interface Pagination {
|
||||
type: PaginationType;
|
||||
selector_type?: SelectorType;
|
||||
selector?: string;
|
||||
max_pages?: number;
|
||||
start_page?: number;
|
||||
}
|
||||
type Pagination = BaseSelector;
|
||||
|
||||
interface PagePattern {
|
||||
name: string;
|
||||
@@ -84,7 +80,18 @@ export declare global {
|
||||
}
|
||||
|
||||
interface AutoProbeNavItem<T = any> extends NavItem<T> {
|
||||
type?: 'page_pattern' | 'fields' | 'lists' | 'pagination' | 'list' | 'item' | 'field';
|
||||
name?: string;
|
||||
type?:
|
||||
| 'page_pattern'
|
||||
| 'fields'
|
||||
| 'lists'
|
||||
| 'pagination'
|
||||
| 'list'
|
||||
| 'item'
|
||||
| 'field';
|
||||
children?: AutoProbeNavItem[];
|
||||
fieldCount?: number;
|
||||
field?: FieldRule;
|
||||
pagination?: Pagination;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,10 @@ const processListItem = (list: ListRule): AutoProbeNavItem => {
|
||||
children.push({
|
||||
id: `${list.name}-${field.name}`,
|
||||
label: field.name,
|
||||
name: field.name,
|
||||
icon: ['fa', 'tag'],
|
||||
type: 'field',
|
||||
field,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -53,6 +55,7 @@ const processListItem = (list: ListRule): AutoProbeNavItem => {
|
||||
return {
|
||||
id: list.name,
|
||||
label: `${list.name} (${children.length})`,
|
||||
name: list.name,
|
||||
type: 'list',
|
||||
icon: ['fa', 'list'],
|
||||
children,
|
||||
@@ -69,8 +72,10 @@ const computedTreeItems = computed<AutoProbeNavItem[]>(() => {
|
||||
children.push({
|
||||
id: field.name,
|
||||
label: field.name,
|
||||
name: field.name,
|
||||
icon: ['fa', 'tag'],
|
||||
type: 'field',
|
||||
field,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -87,8 +92,10 @@ const computedTreeItems = computed<AutoProbeNavItem[]>(() => {
|
||||
children.push({
|
||||
id: 'pagination',
|
||||
label: t('components.autoprobe.navItems.pagination'),
|
||||
name: t('components.autoprobe.navItems.pagination'),
|
||||
type: 'pagination',
|
||||
icon: ['fa', 'pager'],
|
||||
icon: ['fa', 'ellipsis-h'],
|
||||
pagination: pagePagination.value,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -96,6 +103,7 @@ const computedTreeItems = computed<AutoProbeNavItem[]>(() => {
|
||||
{
|
||||
id: 'page',
|
||||
label: `${form.value.page_pattern.name} (${children.length})`,
|
||||
name: form.value.page_pattern.name,
|
||||
type: 'page_pattern',
|
||||
icon: ['fa', 'network-wired'],
|
||||
children,
|
||||
@@ -120,10 +128,10 @@ const getFieldData = (fieldName: string) => {
|
||||
// Function to get data for a specific navigation item
|
||||
const getNavItemData = (item: AutoProbeNavItem) => {
|
||||
if (!pageData.value) return undefined;
|
||||
|
||||
|
||||
// Convert to Record<string, any> to handle dynamic property access
|
||||
const data = pageData.value as Record<string, any>;
|
||||
|
||||
|
||||
switch (item.type) {
|
||||
case 'field':
|
||||
// For fields, extract from page data by field name
|
||||
@@ -330,28 +338,25 @@ defineOptions({ name: 'ClAutoProbeDetailTabPatterns' });
|
||||
</div>
|
||||
<div class="content">
|
||||
<template v-if="activeNavItem?.type === 'field'">
|
||||
<cl-auto-probe-field-detail
|
||||
:field="activeNavItem"
|
||||
:page-data="getNavItemData(activeNavItem)"
|
||||
<cl-auto-probe-field-detail
|
||||
:field="activeNavItem"
|
||||
:page-data="getNavItemData(activeNavItem)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="activeNavItem?.type === 'list'">
|
||||
<cl-auto-probe-list-detail
|
||||
:list="activeNavItem"
|
||||
:page-data="getNavItemData(activeNavItem)"
|
||||
<cl-auto-probe-list-detail
|
||||
:list="activeNavItem"
|
||||
:page-data="getNavItemData(activeNavItem)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="activeNavItem?.type === 'pagination'">
|
||||
<cl-auto-probe-pagination-detail
|
||||
:pagination="activeNavItem"
|
||||
:page-data="getNavItemData(activeNavItem)"
|
||||
<cl-auto-probe-pagination-detail
|
||||
:pagination="activeNavItem"
|
||||
:page-data="getNavItemData(activeNavItem)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="activeNavItem?.type === 'page_pattern'">
|
||||
<cl-auto-probe-page-pattern-detail
|
||||
:page-pattern="activeNavItem"
|
||||
:page-data="getNavItemData(activeNavItem)"
|
||||
/>
|
||||
<cl-auto-probe-page-pattern-detail :page-pattern="activeNavItem" />
|
||||
</template>
|
||||
<div v-else class="placeholder">
|
||||
{{
|
||||
@@ -464,7 +469,6 @@ defineOptions({ name: 'ClAutoProbeDetailTabPatterns' });
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
|
||||
.detail-panel {
|
||||
|
||||
@@ -11,7 +11,12 @@ import {
|
||||
FILTER_OP_CONTAINS,
|
||||
TABLE_COLUMN_NAME_ACTIONS,
|
||||
} from '@/constants';
|
||||
import { getIconByAction, onListFilterChangeByKey, translate } from '@/utils';
|
||||
import {
|
||||
getIconByAction,
|
||||
onListFilterChangeByKey,
|
||||
setupAutoUpdate,
|
||||
translate,
|
||||
} from '@/utils';
|
||||
import {
|
||||
ClNavLink,
|
||||
ClAutoProbeTaskStatus,
|
||||
@@ -30,7 +35,7 @@ const useAutoProbeList = () => {
|
||||
const { commit } = store;
|
||||
|
||||
const { actionFunctions } = useList<AutoProbe>(ns, store);
|
||||
const { deleteByIdConfirm } = actionFunctions;
|
||||
const { getList, deleteByIdConfirm } = actionFunctions;
|
||||
|
||||
// nav actions
|
||||
const navActions = computed<ListActionGroup[]>(() => [
|
||||
@@ -185,6 +190,8 @@ const useAutoProbeList = () => {
|
||||
] as TableColumns<AutoProbe>
|
||||
);
|
||||
|
||||
setupAutoUpdate(getList);
|
||||
|
||||
return {
|
||||
...useList<AutoProbe>(ns, store),
|
||||
navActions,
|
||||
|
||||
@@ -3,7 +3,12 @@ import { ref, computed, onBeforeMount } from 'vue';
|
||||
import { ElSpace, ElMessage, ElMessageBox, ElCheckbox } from 'element-plus';
|
||||
import { ClTag, ClNavLink, ClIcon } from '@/components';
|
||||
import useRequest from '@/services/request';
|
||||
import { getDefaultPagination, plainClone, translate } from '@/utils';
|
||||
import {
|
||||
EMPTY_OBJECT_ID,
|
||||
getDefaultPagination,
|
||||
plainClone,
|
||||
translate,
|
||||
} from '@/utils';
|
||||
import {
|
||||
ACTION_DELETE,
|
||||
ACTION_EDIT,
|
||||
@@ -37,7 +42,7 @@ const updateDefaultProviderId = async (id: string) => {
|
||||
default_provider_id: id,
|
||||
},
|
||||
};
|
||||
if (!settingAI.value) {
|
||||
if (!settingAI.value?._id || settingAI.value._id === EMPTY_OBJECT_ID) {
|
||||
await post('/settings/ai', { data });
|
||||
} else {
|
||||
await put('/settings/ai', { data });
|
||||
|
||||
Reference in New Issue
Block a user