refactor(@vben/web-antd): 完成数据爬取页面重构

This commit is contained in:
vertoryao 2025-05-05 16:33:06 +08:00
parent d12ea7dd83
commit 83953c07c4
12 changed files with 456 additions and 200 deletions

View File

@ -1,3 +1,5 @@
import type { SpiderItem } from '@vben/common-ui';
import { requestClient } from '#/api/request';
export namespace RepositoryApi {
@ -7,9 +9,9 @@ export namespace RepositoryApi {
}
}
export const getAppList = (params: RepositoryApi.AppListParams) => {
return requestClient.get(`/v1/server/apps`, { params });
};
export async function getAppList(params: RepositoryApi.AppListParams) {
return requestClient.get<SpiderItem[]>(`/v1/server/apps`, { params });
}
// export const getAppDetail = (id) => {
// return requestClient.get(`/v1/server/${id}`)

View File

@ -51,14 +51,13 @@ export const useAuthStore = defineStore('auth', () => {
realName: user.username,
username: user.username,
roles: [user.role.name],
homePath: '/analytics',
homePath: '/home',
token: csrf.token,
};
accessStore.setAccessToken(csrf.token);
userStore.setUserInfo(userInfo);
accessStore.setAccessCodes(user.permissions);
console.log(userStore.userInfo);
if (accessStore.loginExpired) {
accessStore.setLoginExpired(false);
@ -115,7 +114,7 @@ export const useAuthStore = defineStore('auth', () => {
realName: user.username,
username: user.username,
roles: [user.role.name],
homePath: '/analytics',
homePath: '/home',
token: csrf.token,
};
userStore.setUserInfo(userInfo);

View File

@ -1,114 +1,27 @@
<script setup lang="js">
<script lang="ts" setup>
import type { SpiderItem } from '@vben/common-ui';
import { onMounted, ref } from 'vue';
import { SpiderListView, SpiderWorkView } from '@vben/common-ui';
import { getAppList, sendWorkflow } from '#/api';
//
const tools = ref([
{
id: 1,
name: '工具一',
description: '这是一个用于抓取网页数据的工具',
content: '工具一的具体内容:支持多种网页抓取规则配置。',
path: '/tools/tool1',
},
{
id: 2,
name: '工具二',
description: '这是一个用于分析文本数据的工具',
content: '工具二的具体内容:支持自然语言处理和文本分析功能。',
path: '/tools/tool2',
},
{
id: 3,
name: '工具三',
description: '这是一个用于数据清洗的工具',
content: '工具三的具体内容:支持数据去重、格式化等操作。',
path: '/tools/tool3',
},
]);
//
const selectedTool = ref(null);
//
const isFetching = ref(false);
const fetchStatus = ref('idle'); // idle | fetching | completed
//
const fetchResult = ref(null);
//
const selectTool = (tool) => {
selectedTool.value = tool;
fetchStatus.value = 'idle'; //
};
const spiderList = ref<SpiderItem[]>([]);
const loading = ref(true);
const spider = ref<SpiderItem>();
const getChatflowList = async () => {
try {
const res = await getAppList({
mode: '',
name: '',
});
tools.value = res;
} catch (error) {
console.error(error);
const res = await getAppList({});
if (res) {
spiderList.value = res;
loading.value = false;
}
};
//
const startFetching = async () => {
if (!selectedTool.value) return;
//
isFetching.value = true;
fetchStatus.value = 'fetching';
try {
const res = await sendWorkflow(
{
appid: 'c736edd0-925d-4877-9223-56aab7342311',
},
{
userId: '1562',
conversationId: '',
files: [],
inputs: {},
},
);
//
fetchResult.value = res.data.outputs.result;
} catch (error) {
console.error(error);
}
//
isFetching.value = false;
fetchStatus.value = 'completed';
};
//
const downloadFile = () => {
if (!fetchResult.value) return;
// 使
const fileName = `${selectedTool.value.name}.txt`;
const fileContent = fetchResult.value;
// Blob
const blob = new Blob([fileContent], { type: 'text/plain' });
//
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = fileName;
//
link.click();
// URL
URL.revokeObjectURL(link.href);
};
function handleClick(item: SpiderItem) {
spider.value = item;
}
onMounted(() => {
getChatflowList();
@ -116,95 +29,23 @@ onMounted(() => {
</script>
<template>
<a-layout>
<!-- 右侧部分开始 -->
<a-layout :style="{ height: '100vh' }">
<!-- 右侧页面主体开始 -->
<a-layout-content
:style="{
margin: '12px 10px',
padding: '18px',
background: '#fff',
minHeight: '280px',
borderRadius: '4px',
}"
>
<!-- 左右布局容器 -->
<a-row
:gutter="16"
:style="{ height: 'calc(100vh - 100px)', overflow: 'hidden' }"
>
<!-- 左边数据爬取工具列表 -->
<a-col
:span="6"
:style="{
height: '100%',
overflowY: 'auto',
borderRight: '1px solid #e8e8e8',
}"
>
<a-card
title="数据爬取工具列表"
:bordered="false"
:style="{ height: '100%' }"
>
<a-list item-layout="horizontal" :data-source="tools">
<template #renderItem="{ item }">
<a-list-item @click="selectTool(item)">
<a-list-item-meta>
<template #title>
<span>{{ item.name }}</span>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-card>
</a-col>
<!-- 右边所选工具的内容 -->
<a-col
:span="18"
:style="{
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-end',
height: '100%',
}"
>
<a-card
v-if="selectedTool"
title="工具"
:bordered="false"
:style="{ marginBottom: '20px' }"
>
<p>{{ selectedTool.name }}</p>
<!-- 抓取按钮和状态 -->
<div style="float: right; margin-top: 20px">
<a-button
type="primary"
style="margin-right: 12px"
:loading="isFetching"
@click="startFetching"
>
开始抓取
</a-button>
<a-button
type="primary"
@click="downloadFile"
v-if="fetchStatus === 'completed'"
>
下载文件
</a-button>
</div>
</a-card>
<a-empty v-else description="请选择一个工具" />
</a-col>
</a-row>
</a-layout-content>
<!-- 右侧页面主体结束 -->
</a-layout>
<!-- 右侧部分结束 -->
</a-layout>
<div class="px-5">
<div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-1/4">
<SpiderListView
:loading="loading"
:items="spiderList"
title="数据爬取工具"
@click="handleClick"
/>
</div>
<div class="w-full lg:w-3/4">
<SpiderWorkView
:item="spider"
:run-spider="sendWorkflow"
title="目标网址:"
/>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,210 @@
<script setup lang="js">
import { onMounted, ref } from 'vue';
import { getAppList, sendWorkflow } from '#/api';
//
const tools = ref([
{
id: 1,
name: '工具一',
description: '这是一个用于抓取网页数据的工具',
content: '工具一的具体内容:支持多种网页抓取规则配置。',
path: '/tools/tool1',
},
{
id: 2,
name: '工具二',
description: '这是一个用于分析文本数据的工具',
content: '工具二的具体内容:支持自然语言处理和文本分析功能。',
path: '/tools/tool2',
},
{
id: 3,
name: '工具三',
description: '这是一个用于数据清洗的工具',
content: '工具三的具体内容:支持数据去重、格式化等操作。',
path: '/tools/tool3',
},
]);
//
const selectedTool = ref(null);
//
const isFetching = ref(false);
const fetchStatus = ref('idle'); // idle | fetching | completed
//
const fetchResult = ref(null);
//
const selectTool = (tool) => {
selectedTool.value = tool;
fetchStatus.value = 'idle'; //
};
const getChatflowList = async () => {
try {
const res = await getAppList({
mode: '',
name: '',
});
tools.value = res;
} catch (error) {
console.error(error);
}
};
//
const startFetching = async () => {
if (!selectedTool.value) return;
//
isFetching.value = true;
fetchStatus.value = 'fetching';
try {
const res = await sendWorkflow(
{
appid: 'c736edd0-925d-4877-9223-56aab7342311',
},
{
userId: '1562',
conversationId: '',
files: [],
inputs: {},
},
);
//
fetchResult.value = res.data.outputs.result;
} catch (error) {
console.error(error);
}
//
isFetching.value = false;
fetchStatus.value = 'completed';
};
//
const downloadFile = () => {
if (!fetchResult.value) return;
// 使
const fileName = `${selectedTool.value.name}.txt`;
const fileContent = fetchResult.value;
// Blob
const blob = new Blob([fileContent], { type: 'text/plain' });
//
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = fileName;
//
link.click();
// URL
URL.revokeObjectURL(link.href);
};
onMounted(() => {
getChatflowList();
});
</script>
<template>
<a-layout>
<!-- 右侧部分开始 -->
<a-layout :style="{ height: '100vh' }">
<!-- 右侧页面主体开始 -->
<a-layout-content
:style="{
margin: '12px 10px',
padding: '18px',
background: '#fff',
minHeight: '280px',
borderRadius: '4px',
}"
>
<!-- 左右布局容器 -->
<a-row
:gutter="16"
:style="{ height: 'calc(100vh - 100px)', overflow: 'hidden' }"
>
<!-- 左边数据爬取工具列表 -->
<a-col
:span="6"
:style="{
height: '100%',
overflowY: 'auto',
borderRight: '1px solid #e8e8e8',
}"
>
<a-card
title="数据爬取工具列表"
:bordered="false"
:style="{ height: '100%' }"
>
<a-list item-layout="horizontal" :data-source="tools">
<template #renderItem="{ item }">
<a-list-item @click="selectTool(item)">
<a-list-item-meta>
<template #title>
<span>{{ item.name }}</span>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-card>
</a-col>
<!-- 右边所选工具的内容 -->
<a-col
:span="18"
:style="{
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-end',
height: '100%',
}"
>
<a-card
v-if="selectedTool"
title="工具"
:bordered="false"
:style="{ marginBottom: '20px' }"
>
<p>{{ selectedTool.name }}</p>
<!-- 抓取按钮和状态 -->
<div style="float: right; margin-top: 20px">
<a-button
type="primary"
style="margin-right: 12px"
:loading="isFetching"
@click="startFetching"
>
开始抓取
</a-button>
<a-button
type="primary"
@click="downloadFile"
v-if="fetchStatus === 'completed'"
>
下载文件
</a-button>
</div>
</a-card>
<a-empty v-else description="请选择一个工具" />
</a-col>
</a-row>
</a-layout-content>
<!-- 右侧页面主体结束 -->
</a-layout>
<!-- 右侧部分结束 -->
</a-layout>
</template>

View File

@ -40,6 +40,7 @@
"@vben/types": "workspace:*",
"@vueuse/core": "catalog:",
"@vueuse/integrations": "catalog:",
"ant-design-vue": "catalog:",
"qrcode": "catalog:",
"tippy.js": "catalog:",
"vue": "catalog:",

View File

@ -21,7 +21,6 @@ defineOptions({
withDefaults(defineProps<Props>(), {
items: () => [],
});
// const emit = defineEmits<{ (e: 'go', path: string): void }>();
defineEmits(['click']);
</script>

View File

@ -3,3 +3,4 @@ export * from './authentication';
export * from './dashboard';
export * from './fallback';
export * from './home';
export * from './spider';

View File

@ -0,0 +1,3 @@
export { default as SpiderListView } from './spider-list-view.vue';
export { default as SpiderWorkView } from './spider-work-view.vue';
export type * from './typing';

View File

@ -0,0 +1,49 @@
<script setup lang="ts">
import type { SpiderItem } from './typing';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@vben-core/shadcn-ui';
interface Props {
items?: SpiderItem[];
title: string;
loading: boolean;
}
defineOptions({
name: 'SpiderListView',
});
withDefaults(defineProps<Props>(), {
items: () => [],
});
defineEmits(['click']);
</script>
<template>
<Card
v-loading="loading"
style="height: calc(100vh - 120px); overflow-y: auto"
>
<CardHeader class="py-4">
<CardTitle class="text-lg">{{ title }}</CardTitle>
<CardDescription>请选择需要爬取的网站</CardDescription>
</CardHeader>
<CardContent class="flex flex-wrap p-5 pt-0">
<ul class="divide-border w-full divide-y" role="list">
<li
v-for="item in items"
:key="item.id"
@click="$emit('click', item)"
class="flex cursor-pointer justify-between gap-x-6 py-5"
>
{{ item.name }}
</li>
</ul>
</CardContent>
</Card>
</template>

View File

@ -0,0 +1,139 @@
<script setup lang="ts">
import type { SpiderItem } from './typing';
import { ref } from 'vue';
import {
Button,
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from '@vben-core/shadcn-ui';
interface SpiderParams {
appid: string;
}
interface SpiderContext {
userId: string;
conversationId: string;
files: unknown[];
inputs: Record<string, unknown>;
}
interface SpiderResult {
data: {
outputs: {
result: string;
};
};
}
interface Props {
item?: SpiderItem;
title: string;
runSpider?: (
params: SpiderParams,
context: SpiderContext,
) => Promise<SpiderResult>;
}
defineOptions({
name: 'SpiderWorkView',
});
const props = withDefaults(defineProps<Props>(), {
item: () => {
return null;
},
runSpider: () => async () => ({
data: {
outputs: {
result: '',
},
},
}),
});
const startFetching = async () => {
//
isFetching.value = true;
fetchStatus.value = 'fetching';
try {
const res = await props.runSpider(
{
appid: 'c736edd0-925d-4877-9223-56aab7342311',
},
{
userId: '1562',
conversationId: '',
files: [],
inputs: {},
},
);
//
fetchResult.value = res.data.outputs.result;
} catch (error) {
console.error(error);
}
//
isFetching.value = false;
fetchStatus.value = 'completed';
};
//
const downloadFile = () => {
if (!fetchResult.value) return;
// 使
const fileName = `${props.item.name}.txt`;
const fileContent = fetchResult.value;
// Blob
const blob = new Blob([fileContent], { type: 'text/plain' });
//
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = fileName;
//
link.click();
// URL
URL.revokeObjectURL(link.href);
};
const isFetching = ref(false);
const fetchResult = ref('');
const fetchStatus = ref('');
</script>
<template>
<div class="flex h-full">
<Card class="w-full self-end" v-loading="isFetching">
<CardHeader class="py-4">
<CardTitle class="text-lg">{{ title }}</CardTitle>
</CardHeader>
<CardContent class="flex flex-wrap pt-0">
{{ item ? item.name : '请选择右侧爬虫' }}
</CardContent>
<CardFooter class="flex justify-end">
<Button :disabled="!item" @click="startFetching">
{{ isFetching ? '抓取中...' : '开始抓取' }}
</Button>
<Button
class="mx-2"
:disabled="fetchStatus !== 'completed'"
@click="downloadFile"
>
下载文件
</Button>
</CardFooter>
</Card>
</div>
</template>

View File

@ -0,0 +1,9 @@
interface SpiderItem {
id: number;
name: string;
description: string;
content: string;
path: string;
}
export type { SpiderItem };

View File

@ -1329,6 +1329,9 @@ importers:
'@vueuse/integrations':
specifier: 'catalog:'
version: 13.1.0(async-validator@4.2.5)(axios@1.9.0)(focus-trap@7.6.4)(jwt-decode@4.0.0)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.6)(vue@3.5.13(typescript@5.8.3))
ant-design-vue:
specifier: 'catalog:'
version: 4.2.6(vue@3.5.13(typescript@5.8.3))
qrcode:
specifier: 'catalog:'
version: 1.5.4