refactor(@vben/web-antd): 完成ppt和word部分功能

This commit is contained in:
vertoryao 2025-05-06 01:46:24 +08:00
parent ba8fc04c94
commit 35a2ad65ca
16 changed files with 553 additions and 68 deletions

View File

@ -1,31 +1,35 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { SpiderItem } from '@vben/common-ui'; import type { SpiderItem } from '@vben/common-ui';
import { onMounted, ref } from 'vue'; import { ref } from 'vue';
import { SpiderListView, SpiderWorkView } from '@vben/common-ui'; import { SpiderListView, SpiderWorkView } from '@vben/common-ui';
import { getAppList, sendWorkflow } from '#/api'; import { sendWorkflow } from '#/api';
const spiderList = ref<SpiderItem[]>([]); const spiderList = ref<SpiderItem[]>([
const loading = ref(true); {
id: 'a2a55334-a111-45e6-942f-9f3f70af8826',
name: '全国公共资源交易平台_数据爬取',
},
]);
const spider = ref<SpiderItem>(); const spider = ref<SpiderItem>();
const getFlowList = async () => { // const getFlowList = async () => {
const res = await getAppList({}); // const res = await getAppList({});
if (res) { // if (res) {
spiderList.value = res; // spiderList.value = res;
loading.value = false; // loading.value = false;
} // }
}; // };
function handleClick(item: SpiderItem) { function handleClick(item: SpiderItem) {
spider.value = item; spider.value = item;
} }
onMounted(() => { // onMounted(() => {
getFlowList(); // getFlowList();
}); // });
</script> </script>
<template> <template>
@ -33,7 +37,6 @@ onMounted(() => {
<div class="mt-5 flex flex-col lg:flex-row"> <div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-1/4"> <div class="mr-4 w-full lg:w-1/4">
<SpiderListView <SpiderListView
:loading="loading"
:items="spiderList" :items="spiderList"
title="数据爬取工具" title="数据爬取工具"
@click="handleClick" @click="handleClick"

View File

@ -1,30 +1,48 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { SpiderItem } from '@vben/common-ui'; import type { WordTempItem } from '@vben/common-ui';
import { onMounted, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import { SpiderListView, SpiderWorkView } from '@vben/common-ui'; import { WordHistoryView, WordListView, WordWorkView } from '@vben/common-ui';
import { getAppList, sendWorkflow } from '#/api'; import { getChatList, sendChatflow } from '#/api';
const spiderList = ref<SpiderItem[]>([]); let temp = reactive<WordTempItem>({
id: 'baca08c1-e92b-4dc9-a445-3584803f54d4',
name: '职业创新申报书',
});
const hitsory = ref([]);
const loading = ref(true); const loading = ref(true);
const spider = ref<SpiderItem>();
const getFlowList = async () => { const getLogs = async (appid: string) => {
const res = await getAppList({}); loading.value = true;
if (res) { const res = await getChatList(
spiderList.value = res; {
loading.value = false; appid,
} },
{
userId: '1562',
lastId: '',
sortBy: '',
limit: '5',
},
);
// const res = await getChatList({
// appid,
// limit: 5,
// page: 1,
// });
hitsory.value = res.data;
loading.value = false;
}; };
function handleClick(item: SpiderItem) { function handleClick(item: WordTempItem) {
spider.value = item; temp = item;
} }
onMounted(() => { onMounted(() => {
getFlowList(); getLogs(temp.id);
}); });
</script> </script>
@ -32,19 +50,16 @@ onMounted(() => {
<div class="px-5"> <div class="px-5">
<div class="mt-5 flex flex-col lg:flex-row"> <div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-1/4"> <div class="mr-4 w-full lg:w-1/4">
<SpiderListView <WordHistoryView
:loading="loading" :loading="loading"
:items="spiderList" :items="hitsory"
title="数据爬取工具" title="运行历史"
@click="handleClick" @click="handleClick"
/> />
<WordListView title="选择模板" @click="handleClick" />
</div> </div>
<div class="w-full lg:w-3/4"> <div class="w-full lg:w-3/4">
<SpiderWorkView <WordWorkView :item="temp" :run-chatflow="sendChatflow" />
:item="spider"
:run-spider="sendWorkflow"
title="目标网址:"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -13,15 +13,14 @@ export default defineConfig(async () => {
target: 'http://localhost:8081/api', target: 'http://localhost:8081/api',
ws: true, ws: true,
}, },
'/static': {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/static/, ''),
target: 'http://47.112.173.8:6802',
},
'/static/*.docx': { '/static/*.docx': {
changeOrigin: true, changeOrigin: true,
target: 'http://47.112.173.8:6805', target: 'http://47.112.173.8:6805',
}, },
'/static/*.ppt': {
changeOrigin: true,
target: 'http://47.112.173.8:6802',
},
'/v1': { '/v1': {
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/v1/, ''), rewrite: (path) => path.replace(/^\/v1/, ''),

View File

@ -39,6 +39,8 @@
"@vben/icons": "workspace:*", "@vben/icons": "workspace:*",
"@vben/locales": "workspace:*", "@vben/locales": "workspace:*",
"@vben/types": "workspace:*", "@vben/types": "workspace:*",
"@vue-office/docx": "^1.6.3",
"@vue-office/pptx": "^1.0.1",
"@vueuse/core": "catalog:", "@vueuse/core": "catalog:",
"@vueuse/integrations": "catalog:", "@vueuse/integrations": "catalog:",
"ant-design-vue": "catalog:", "ant-design-vue": "catalog:",

View File

@ -5,3 +5,4 @@ export * from './fallback';
export * from './home'; export * from './home';
export * from './ppt'; export * from './ppt';
export * from './spider'; export * from './spider';
export * from './word';

View File

@ -0,0 +1,46 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import VueOfficePptx from '@vue-office/pptx';
// import '@vue-office/pptx/lib/index.css';
const url = ref('');
const pptx = ref('/static/43b7da46a6ca47e5a9d7710d8dcf499c.pptx');
const pptStyle = ref({
height: 'calc(100vh - 100px)',
width: '100%',
margin: 'auto',
});
const renderedHandler = () => {
console.warn('渲染完成');
};
const errorHandler = (err) => {
console.error(`渲染失败:${err}`);
};
const [Drawer, drawerApi] = useVbenDrawer({
onCancel() {
drawerApi.close();
},
onClosed() {
drawerApi.setState({ overlayBlur: 0, placement: 'right' });
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
url.value = drawerApi.getData<Record<string, any>>();
}
},
});
</script>
<template>
<Drawer title="文档预览" :footer="false">
<VueOfficePptx
:src="pptx"
:style="pptStyle"
@rendered="renderedHandler"
@error="errorHandler"
/>
</Drawer>
</template>

View File

@ -1,16 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import type { BubbleListProps } from 'ant-design-x-vue'; import type { BubbleListProps } from 'ant-design-x-vue';
import type { DrawerPlacement } from '@vben/common-ui';
import type { PPTTempItem } from './typing'; import type { PPTTempItem } from './typing';
import { h, ref } from 'vue'; import { h, ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { Card } from '@vben-core/shadcn-ui'; import { Card } from '@vben-core/shadcn-ui';
import { UserOutlined } from '@ant-design/icons-vue'; import { UserOutlined } from '@ant-design/icons-vue';
import { Button, Flex } from 'ant-design-vue'; import { Button, Flex } from 'ant-design-vue';
import { Attachments, BubbleList, Sender } from 'ant-design-x-vue'; import { Attachments, BubbleList, Sender } from 'ant-design-x-vue';
import PptPreview from './ppt-perview.vue';
interface WorkflowParams { interface WorkflowParams {
appid: string; appid: string;
} }
@ -42,7 +48,7 @@ defineOptions({
name: 'SpiderWorkView', name: 'SpiderWorkView',
}); });
const props = withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
item: () => null, item: () => null,
runWorkflow: () => async () => ({ runWorkflow: () => async () => ({
data: { data: {
@ -53,26 +59,36 @@ const props = withDefaults(defineProps<Props>(), {
}), }),
}); });
const [PreviewDrawer, previewDrawerApi] = useVbenDrawer({
//
connectedComponent: PptPreview,
// placement: 'left',
});
function openPreviewDrawer(placement: DrawerPlacement = 'right') {
previewDrawerApi.setState({ placement }).open();
}
const startFetching = async () => { const startFetching = async () => {
isFetching.value = true; isFetching.value = true;
fetchStatus.value = 'fetching'; fetchStatus.value = 'fetching';
try { try {
const res = await props.runWorkflow( // const res = await props.runWorkflow(
{ // {
appid: props.item.id, // appid: props.item.id,
}, // },
{ // {
userId: '1562', // userId: '1562',
conversationId: '', // conversationId: '',
files: [], // files: [],
inputs: { // inputs: {
declarationDoc: value.value, // declarationDoc: value.value,
}, // },
}, // },
); // );
// url // url
fetchResult.value = res.data.outputs.result; fetchResult.value = '/static/43b7da46a6ca47e5a9d7710d8dcf499c.pptx';
resultItems.value.push({ resultItems.value.push({
key: resultItems.value.length + 1, key: resultItems.value.length + 1,
role: 'ai', role: 'ai',
@ -83,7 +99,9 @@ const startFetching = async () => {
{ {
size: 'nomarl', size: 'nomarl',
type: 'primary', type: 'primary',
onClick: () => {}, onClick: () => {
openPreviewDrawer('right');
},
}, },
'文档预览', '文档预览',
), ),
@ -162,6 +180,7 @@ const resultItems = ref<ResultItem[]>([]);
<template> <template>
<div> <div>
<PreviewDrawer />
<div style="height: calc(67vh - 120px); margin: 20px; overflow-y: auto"> <div style="height: calc(67vh - 120px); margin: 20px; overflow-y: auto">
<BubbleList :roles="roles" :items="resultItems" /> <BubbleList :roles="roles" :items="resultItems" />
</div> </div>

View File

@ -12,7 +12,6 @@ import {
interface Props { interface Props {
items?: SpiderItem[]; items?: SpiderItem[];
title: string; title: string;
loading: boolean;
} }
defineOptions({ defineOptions({
@ -25,10 +24,7 @@ defineEmits(['click']);
</script> </script>
<template> <template>
<Card <Card style="height: calc(100vh - 120px); overflow-y: auto">
v-loading="loading"
style="height: calc(100vh - 120px); overflow-y: auto"
>
<CardHeader class="py-4"> <CardHeader class="py-4">
<CardTitle class="text-lg">{{ title }}</CardTitle> <CardTitle class="text-lg">{{ title }}</CardTitle>
<CardDescription>请选择需要爬取的网站</CardDescription> <CardDescription>请选择需要爬取的网站</CardDescription>

View File

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

View File

@ -0,0 +1,4 @@
export type * from './typing';
export { default as WordHistoryView } from './word-history-view.vue';
export { default as WordListView } from './word-list-view.vue';
export { default as WordWorkView } from './word-work-view.vue';

View File

@ -0,0 +1,17 @@
interface WordTempItem {
id: string;
name: string;
}
interface WordHistoryItem {
id: string;
workflowRun: {
id: string;
};
createdByEndUser: {
id: string;
sessionId: string;
};
}
export type { WordHistoryItem, WordTempItem };

View File

@ -0,0 +1,43 @@
<script setup lang="ts">
import type { WordHistoryItem } from '../word';
import { Card, CardContent, CardHeader, CardTitle } from '@vben-core/shadcn-ui';
interface Props {
items?: WordHistoryItem[];
title: string;
loading: boolean;
}
defineOptions({
name: 'WordHistoryView',
});
withDefaults(defineProps<Props>(), {
items: () => [],
});
defineEmits(['click']);
</script>
<template>
<Card
style="height: calc(55vh - 120px); margin-bottom: 20px; overflow-y: auto"
>
<CardHeader class="py-4">
<CardTitle class="text-lg">运行历史</CardTitle>
</CardHeader>
<CardContent class="flex flex-wrap p-5 pt-0">
<ul class="divide-border w-full divide-y" role="list">
<li
v-for="(item, index) in items"
:key="index"
@click="$emit('click', item)"
class="flex cursor-pointer justify-between gap-x-6 py-5"
>
{{ item.id }}
</li>
</ul>
</CardContent>
</Card>
</template>

View File

@ -0,0 +1,38 @@
<script setup lang="ts">
import type { WordTempItem } from './typing';
import { Card, CardContent, CardHeader, CardTitle } from '@vben-core/shadcn-ui';
defineOptions({
name: 'WordListView',
});
defineEmits(['click']);
const items: WordTempItem[] = [
{
id: 'baca08c1-e92b-4dc9-a445-3584803f54d4',
name: '职业创新申报书',
},
];
</script>
<template>
<Card style="height: calc(55vh - 120px); overflow-y: auto">
<CardHeader class="py-4">
<CardTitle class="text-lg">选择模板</CardTitle>
</CardHeader>
<CardContent class="flex flex-wrap p-5 pt-0">
<ul class="divide-border w-full divide-y" role="list">
<li
v-for="(item, index) in items"
:key="index"
@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,46 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import VueOfficeDocx from '@vue-office/docx';
// import '@vue-office/pptx/lib/index.css';
const url = ref('');
const docx = ref<any>('/static/9e1c421e-991c-411f-8176-6350a97e70f3.docx');
const pptStyle = ref({
height: 'calc(100vh - 100px)',
width: '100%',
margin: 'auto',
});
const renderedHandler = () => {
console.warn('渲染完成');
};
const errorHandler = (err) => {
console.error(`渲染失败:${err}`);
};
const [Drawer, drawerApi] = useVbenDrawer({
onCancel() {
drawerApi.close();
},
onClosed() {
drawerApi.setState({ overlayBlur: 0, placement: 'right' });
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
url.value = drawerApi.getData<Record<string, any>>();
}
},
});
</script>
<template>
<Drawer title="文档预览" :footer="false">
<VueOfficeDocx
:src="docx"
:style="pptStyle"
@rendered="renderedHandler"
@error="errorHandler"
/>
</Drawer>
</template>

View File

@ -0,0 +1,253 @@
<script setup lang="ts">
import type { BubbleListProps } from 'ant-design-x-vue';
import type { DrawerPlacement } from '@vben/common-ui';
import type { WordTempItem } from './typing';
import { h, ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { Card } from '@vben-core/shadcn-ui';
import { UserOutlined } from '@ant-design/icons-vue';
import { Button, Flex } from 'ant-design-vue';
import { Attachments, BubbleList, Sender } from 'ant-design-x-vue';
import WordPreview from './word-preview.vue';
interface WorkflowParams {
appid: string;
}
interface WorkflowContext {
userId: string;
conversationId: string;
content: string;
inputs: {
[key: string]: any;
};
files: [];
}
interface WorkflowResult {
data: {
outputs: {
result: string;
};
};
}
interface Props {
item?: WordTempItem;
runChatflow?: (
params: WorkflowParams,
context: WorkflowContext,
) => Promise<WorkflowResult>;
}
defineOptions({
name: 'SpiderWorkView',
});
const props = withDefaults(defineProps<Props>(), {
item: () => null,
runChatflow: () => async () => ({
data: {
outputs: {
result: '',
},
},
}),
});
const [PreviewDrawer, previewDrawerApi] = useVbenDrawer({
//
connectedComponent: WordPreview,
// placement: 'left',
});
function openPreviewDrawer(placement: DrawerPlacement = 'right') {
previewDrawerApi.setState({ placement }).open();
}
const startFetching = async () => {
isFetching.value = true;
fetchStatus.value = 'fetching';
resultItems.value.push({
key: resultItems.value.length + 1,
role: 'user',
content: value.value,
});
try {
// const res = await props.runWorkflow(
// {
// appid: props.item.id,
// },
// {
// userId: '1562',
// conversationId: '',
// files: [],
// inputs: {
// declarationDoc: value.value,
// },
// },
// );
// url
const res = await props.runChatflow(
{
appid: 'baca08c1-e92b-4dc9-a445-3584803f54d4',
},
{
userId: '1562',
conversationId: '',
files: [],
inputs: {
projectName: projectName.value,
},
content: value.value || '',
},
);
fetchResult.value = '/static/9e1c421e-991c-411f-8176-6350a97e70f3.docx';
resultItems.value.push({
key: resultItems.value.length + 1,
role: 'ai',
content: res.answer,
footer: h(Flex, null, [
h(
Button,
{
size: 'nomarl',
type: 'primary',
onClick: () => {
openPreviewDrawer('right');
},
},
'文档预览',
),
h(
Button,
{
size: 'nomarl',
type: 'primary',
style: {
marginLeft: '10px',
},
onClick: () => {},
},
'文档下载',
),
]),
});
// resultItems.value.push({
// key: resultItems.value.length + 1,
// role: 'ai',
// content: '',
// footer: h(Flex, null, [
// h(
// Button,
// {
// size: 'nomarl',
// type: 'primary',
// onClick: () => {
// openPreviewDrawer('right');
// },
// },
// '',
// ),
// h(
// Button,
// {
// size: 'nomarl',
// type: 'primary',
// style: {
// marginLeft: '10px',
// },
// onClick: () => {},
// },
// '',
// ),
// ]),
// });
} catch (error) {
console.error(error);
}
//
isFetching.value = false;
fetchStatus.value = 'completed';
};
const roles: BubbleListProps['roles'] = {
user: {
placement: 'end',
typing: false,
avatar: { icon: h(UserOutlined), style: { background: '#87d068' } },
},
ai: {
placement: 'start',
typing: false,
style: {
maxWidth: 600,
marginInlineEnd: 44,
},
styles: {
footer: {
width: '100%',
},
},
avatar: { icon: h(UserOutlined), style: { background: '#fde3cf' } },
},
file: {
placement: 'start',
avatar: { icon: h(UserOutlined), style: { visibility: 'hidden' } },
variant: 'borderless',
messageRender: (items: any) =>
h(
Flex,
{ vertical: true, gap: 'middle' },
items.map((item: any) =>
h(Attachments.FileCard, { key: item.uid, item }),
),
),
},
};
const isFetching = ref(false);
const fetchResult = ref('');
const fetchStatus = ref('');
const projectName = ref<string>('');
const value = ref('');
interface ResultItem {
key: number;
role: 'ai' | 'user';
content: string;
footer?: any;
}
const resultItems = ref<ResultItem[]>([]);
</script>
<template>
<div>
<PreviewDrawer />
<a-space class="w-full" direction="vertical">
<a-input v-model:value="projectName" required placeholder="项目名称" />
</a-space>
<div style="height: calc(67vh - 120px); margin: 20px; overflow-y: auto">
<BubbleList :roles="roles" :items="resultItems" />
</div>
<Card class="w-full">
<Sender
v-model:value="value"
:loading="isFetching"
:auto-size="{ minRows: 6, maxRows: 12 }"
@submit="startFetching"
/>
</Card>
</div>
</template>

View File

@ -1326,6 +1326,12 @@ importers:
'@vben/types': '@vben/types':
specifier: workspace:* specifier: workspace:*
version: link:../../types version: link:../../types
'@vue-office/docx':
specifier: ^1.6.3
version: 1.6.3(vue-demi@0.14.10(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
'@vue-office/pptx':
specifier: ^1.0.1
version: 1.0.1(vue-demi@0.14.10(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
'@vueuse/core': '@vueuse/core':
specifier: 'catalog:' specifier: 'catalog:'
version: 13.1.0(vue@3.5.13(typescript@5.8.3)) version: 13.1.0(vue@3.5.13(typescript@5.8.3))