feat(@vben/web-antd): 优化部分功能

This commit is contained in:
Kven 2025-05-14 19:48:01 +08:00
parent d860741384
commit dcafd5e3b0
13 changed files with 217 additions and 115 deletions

View File

@ -1,6 +1,6 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { EosRole, IconSystem, MdiUser, RiDept } from '@vben/icons'; import { EosRole, IconLog, IconSystem, MdiUser, RiDept } from '@vben/icons';
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{ {
@ -48,7 +48,7 @@ const routes: RouteRecordRaw[] = [
path: '/system/log', path: '/system/log',
component: () => import('#/views/log/list.vue'), component: () => import('#/views/log/list.vue'),
meta: { meta: {
icon: MdiUser, icon: IconLog,
title: '系统日志', title: '系统日志',
authority: ['system'], authority: ['system'],
}, },

View File

@ -5,6 +5,8 @@ import { onMounted, reactive, ref } from 'vue';
import { PptHistoryView, PptListView, PptWorkView } from '@vben/common-ui'; import { PptHistoryView, PptListView, PptWorkView } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { getWorkflowInfo, getWorkflowList, sendWorkflow } from '#/api'; import { getWorkflowInfo, getWorkflowList, sendWorkflow } from '#/api';
let temp = reactive<PPTTempItem>({ let temp = reactive<PPTTempItem>({
@ -57,6 +59,7 @@ async function handleClick(item: PPTTempItem) {
} }
async function handleClickMode(item: PPTTempItem) { async function handleClickMode(item: PPTTempItem) {
message.success(`已选取${item.name}为模板`);
temp = item; temp = item;
} }
@ -66,9 +69,9 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="px-5"> <div class="pr-5" style="height: calc(100vh - 80px)">
<div class="mt-5 flex flex-col lg:flex-row"> <div class="flex h-full flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-1/4"> <div class="mr-4 w-full lg:w-1/6">
<PptHistoryView <PptHistoryView
:loading="loading" :loading="loading"
:items="hitsory" :items="hitsory"
@ -77,7 +80,7 @@ onMounted(() => {
/> />
<PptListView title="选择模板" @click="handleClickMode" /> <PptListView title="选择模板" @click="handleClickMode" />
</div> </div>
<div class="w-full lg:w-3/4"> <div class="h-full w-full lg:w-5/6">
<PptWorkView <PptWorkView
:item="temp" :item="temp"
:run-workflow="sendWorkflow" :run-workflow="sendWorkflow"

View File

@ -12,6 +12,10 @@ const spiderList = ref<SpiderItem[]>([
id: 'a2a55334-a111-45e6-942f-9f3f70af8826', id: 'a2a55334-a111-45e6-942f-9f3f70af8826',
name: '全国公共资源交易平台_数据爬取', name: '全国公共资源交易平台_数据爬取',
}, },
{
id: 'c736edd0-925d-4877-9223-56aab7342311',
name: '广州公共资源交易中心',
},
]); ]);
const spider = ref<SpiderItem>(); const spider = ref<SpiderItem>();
@ -35,14 +39,14 @@ function handleClick(item: SpiderItem) {
<template> <template>
<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/5">
<SpiderListView <SpiderListView
:items="spiderList" :items="spiderList"
title="数据爬取工具" title="数据爬取工具"
@click="handleClick" @click="handleClick"
/> />
</div> </div>
<div class="w-full lg:w-3/4"> <div class="w-full lg:w-4/5">
<SpiderWorkView <SpiderWorkView
:item="spider" :item="spider"
:run-spider="sendWorkflow" :run-spider="sendWorkflow"

View File

@ -5,6 +5,8 @@ import { onMounted, reactive, ref } from 'vue';
import { WordHistoryView, WordListView, WordWorkView } from '@vben/common-ui'; import { WordHistoryView, WordListView, WordWorkView } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { getChatList, sendChatflow } from '#/api'; import { getChatList, sendChatflow } from '#/api';
let temp = reactive<WordTempItem>({ let temp = reactive<WordTempItem>({
@ -37,7 +39,13 @@ const getLogs = async (appid: string) => {
loading.value = false; loading.value = false;
}; };
function handleClickMode(item: WordTempItem) {
message.success(`已选取${item.name}为模板`);
temp = item;
}
function handleClick(item: WordTempItem) { function handleClick(item: WordTempItem) {
message.error('暂不支持查看历史');
temp = item; temp = item;
} }
@ -47,18 +55,18 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="px-5"> <div class="pr-5" style="height: calc(100vh - 80px)">
<div class="mt-5 flex flex-col lg:flex-row"> <div class="flex h-full flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-1/4"> <div class="mr-4 w-full lg:w-1/6">
<WordHistoryView <WordHistoryView
:loading="loading" :loading="loading"
:items="hitsory" :items="hitsory"
title="运行历史" title="运行历史"
@click="handleClick" @click="handleClick"
/> />
<WordListView title="选择模板" @click="handleClick" /> <WordListView title="选择模板" @click="handleClickMode" />
</div> </div>
<div class="w-full lg:w-3/4"> <div class="h-full w-full lg:w-5/6">
<WordWorkView :item="temp" :run-chatflow="sendChatflow" /> <WordWorkView :item="temp" :run-chatflow="sendChatflow" />
</div> </div>
</div> </div>

View File

@ -23,9 +23,7 @@ defineEmits(['click']);
</script> </script>
<template> <template>
<Card <Card style="height: 45vh; overflow-y: auto; border-radius: 0">
style="height: calc(55vh - 120px); margin-bottom: 20px; overflow-y: auto"
>
<CardHeader class="py-4"> <CardHeader class="py-4">
<CardTitle class="text-lg">运行历史</CardTitle> <CardTitle class="text-lg">运行历史</CardTitle>
</CardHeader> </CardHeader>

View File

@ -18,7 +18,7 @@ const items: PPTTempItem[] = [
</script> </script>
<template> <template>
<Card style="height: calc(55vh - 120px); overflow-y: auto"> <Card style="height: 50vh; overflow-y: auto; border-radius: 0">
<CardHeader class="py-4"> <CardHeader class="py-4">
<CardTitle class="text-lg">选择模板</CardTitle> <CardTitle class="text-lg">选择模板</CardTitle>
</CardHeader> </CardHeader>

View File

@ -12,9 +12,8 @@ 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, Typography } 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 markdownit from 'markdown-it';
import PptPreview from './ppt-perview.vue'; import PptPreview from './ppt-perview.vue';
@ -69,24 +68,6 @@ const props = withDefaults(defineProps<Props>(), {
}), }),
}); });
// markdown
const md = markdownit({ html: true, breaks: true });
const renderMarkdown: BubbleListProps['roles'][string]['messageRender'] = (
content: string,
) => {
return h(Typography, [
h('div', {
innerHTML: content,
style: {
padding: '8px',
lineHeight: '1.6',
whiteSpace: 'break-spaces',
},
}),
]);
};
const [PreviewDrawer, previewDrawerApi] = useVbenDrawer({ const [PreviewDrawer, previewDrawerApi] = useVbenDrawer({
// //
connectedComponent: PptPreview, connectedComponent: PptPreview,
@ -134,6 +115,7 @@ const startFetching = async () => {
}, },
); );
const { result } = res.data.outputs; const { result } = res.data.outputs;
value.value = '';
const filename = ref(''); const filename = ref('');
@ -209,17 +191,6 @@ const roles: BubbleListProps['roles'] = {
}, },
}, },
avatar: { icon: h(UserOutlined), style: { background: '#fde3cf' } }, avatar: { icon: h(UserOutlined), style: { background: '#fde3cf' } },
messageRender: (content) =>
h(Typography, [
h('div', {
innerHTML: md.render(content),
style: {
padding: '8px',
lineHeight: '1.6',
whiteSpace: 'break-spaces',
},
}),
]),
}, },
file: { file: {
placement: 'start', placement: 'start',
@ -316,14 +287,14 @@ watch(
</script> </script>
<template> <template>
<div> <div style="display: flex; flex-direction: column" class="h-full">
<PreviewDrawer /> <PreviewDrawer />
<div style="height: calc(67vh - 120px); margin: 20px; overflow-y: auto"> <div style="flex: 1; padding: 20px; overflow-y: auto">
<BubbleList <BubbleList
variant="shadow"
:roles="roles" :roles="roles"
:typing="true" :typing="true"
:items="resultItems" :items="resultItems"
:message-render="renderMarkdown"
/> />
</div> </div>
<Card class="w-full"> <Card class="w-full">
@ -331,9 +302,15 @@ watch(
v-model:value="value" v-model:value="value"
:loading="isFetching" :loading="isFetching"
:disabled="isFetching" :disabled="isFetching"
:auto-size="{ minRows: 6, maxRows: 12 }" :auto-size="{ minRows: 1, maxRows: 12 }"
@submit="startFetching" @submit="startFetching"
/> />
</Card> </Card>
</div> </div>
</template> </template>
<style scoped>
.markdown-content p:empty {
display: none;
}
</style>

View File

@ -13,7 +13,11 @@ import {
} from '@vben-core/shadcn-ui'; } from '@vben-core/shadcn-ui';
import { message, RangePicker } from 'ant-design-vue'; import { message, RangePicker } from 'ant-design-vue';
import dayjs from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
// import {Attachments, BubbleList, type BubbleListProps} from "ant-design-x-vue";
// import {UserOutlined} from "@ant-design/icons-vue";
// import PptPreview from "../ppt/ppt-perview.vue";
// import {type DrawerPlacement, useVbenDrawer} from "@vben-core/popup-ui";
interface SpiderParams { interface SpiderParams {
appid: string; appid: string;
@ -34,6 +38,13 @@ interface SpiderResult {
}; };
} }
// interface ResultItem {
// key: number;
// role: 'ai' | 'user';
// content: string;
// footer?: any;
// }
interface Props { interface Props {
item?: SpiderItem; item?: SpiderItem;
title: string; title: string;
@ -60,7 +71,40 @@ const props = withDefaults(defineProps<Props>(), {
}), }),
}); });
const selectedDateRange = ref<string[]>([]); // const resultItems = ref<ResultItem[]>([]);
// const [PreviewDrawer, previewDrawerApi] = useVbenDrawer({
// //
// connectedComponent: PptPreview,
// // placement: 'left',
// });
// function openPreviewDrawer(
// placement: DrawerPlacement = 'right',
// filename?: string,
// ) {
// const fileData = filename.value;
// previewDrawerApi.setState({ placement }).setData(fileData).open();
// }
const selectedDateRange = ref<[Dayjs, Dayjs]>([
dayjs('2025-05-05'),
dayjs('2025-05-07'),
]);
// .docx URL
// function isDocxURL(str: string): boolean {
// return str.endsWith('.docx');
// }
//
// // 使 /static/
// function extractDocxFilename(url: string): null | string {
// const match = url.match(/\/static\/([a-f0-9-]+\.docx)$/i);
// if (match) {
// return match[1]; // 9e1c421e-991c-411f-8176-6350a97e70f3.docx
// }
// return null;
// }
const startFetching = async () => { const startFetching = async () => {
// //
@ -100,6 +144,54 @@ const startFetching = async () => {
fetchResult.value = ''; fetchResult.value = '';
message.error('抓取无结果'); message.error('抓取无结果');
} }
// const { result } = res.data.outputs;
//
// const filename = ref('');
//
// if (result && isDocxURL(result)) {
// filename.value = extractDocxFilename(result);
// }
//
// // url http://47.112.173.8:6802/static/66f3cfd95e364a239d8036390db658ae.docx
// fetchResult.value = `/static/${filename.value}`;
// resultItems.value.push({
// key: resultItems.value.length + 1,
// role: 'ai',
// content: '',
// footer: h(Flex, null, [
// h(
// Button,
// {
// size: 'nomarl',
// type: 'primary',
// onClick: () => {
// openPreviewDrawer('right', filename);
// },
// },
// '',
// ),
// h(
// Button,
// {
// size: 'nomarl',
// type: 'primary',
// style: {
// marginLeft: '10px',
// },
// onClick: () => {
// // <a>
// const link = document.createElement('a');
// link.href = result; //
// link.download = filename; //
// document.body.append(link); // <a>
// link.click(); //
// link.remove(); // <a>
// },
// },
// '',
// ),
// ]),
// });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@ -127,13 +219,59 @@ const downloadFile = () => {
link.click(); link.click();
}; };
//
// 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 isFetching = ref(false);
const fetchResult = ref(''); const fetchResult = ref('');
const fetchStatus = ref(''); const fetchStatus = ref('');
</script> </script>
<template> <template>
<!-- style="flex-direction: column"-->
<div class="flex h-full"> <div class="flex h-full">
<!-- <PreviewDrawer />-->
<!-- <div style="flex:1; padding: 20px; overflow-y: auto">-->
<!-- <BubbleList-->
<!-- variant="shadow"-->
<!-- :roles="roles"-->
<!-- :typing="true"-->
<!-- :items="resultItems"-->
<!-- />-->
<!-- </div>-->
<Card class="w-full self-end" v-loading="isFetching"> <Card class="w-full self-end" v-loading="isFetching">
<CardHeader class="py-4"> <CardHeader class="py-4">
<CardTitle class="text-lg">{{ title }}</CardTitle> <CardTitle class="text-lg">{{ title }}</CardTitle>

View File

@ -4,6 +4,7 @@ interface WordTempItem {
} }
interface WordHistoryItem { interface WordHistoryItem {
name: string;
id: string; id: string;
workflowRun: { workflowRun: {
id: string; id: string;

View File

@ -21,9 +21,7 @@ defineEmits(['click']);
</script> </script>
<template> <template>
<Card <Card style="height: 45vh; overflow-y: auto; border-radius: 0">
style="height: calc(55vh - 120px); margin-bottom: 20px; overflow-y: auto"
>
<CardHeader class="py-4"> <CardHeader class="py-4">
<CardTitle class="text-lg">运行历史</CardTitle> <CardTitle class="text-lg">运行历史</CardTitle>
</CardHeader> </CardHeader>
@ -35,7 +33,7 @@ defineEmits(['click']);
@click="$emit('click', item)" @click="$emit('click', item)"
class="flex cursor-pointer justify-between gap-x-6 py-5" class="flex cursor-pointer justify-between gap-x-6 py-5"
> >
{{ item.id }} {{ item.name }}
</li> </li>
</ul> </ul>
</CardContent> </CardContent>

View File

@ -18,7 +18,7 @@ const items: WordTempItem[] = [
</script> </script>
<template> <template>
<Card style="height: calc(55vh - 120px); overflow-y: auto"> <Card style="height: 50vh; overflow-y: auto; border-radius: 0">
<CardHeader class="py-4"> <CardHeader class="py-4">
<CardTitle class="text-lg">选择模板</CardTitle> <CardTitle class="text-lg">选择模板</CardTitle>
</CardHeader> </CardHeader>

View File

@ -12,7 +12,7 @@ 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, Typography } from 'ant-design-vue'; import { Button, Flex, message, Typography } from 'ant-design-vue';
import { Attachments, BubbleList, Sender } from 'ant-design-x-vue'; import { Attachments, BubbleList, Sender } from 'ant-design-x-vue';
import markdownit from 'markdown-it'; import markdownit from 'markdown-it';
@ -33,6 +33,8 @@ interface WorkflowContext {
} }
interface WorkflowResult { interface WorkflowResult {
conversationId: string;
answer: {};
data: { data: {
outputs: { outputs: {
result: string; result: string;
@ -73,7 +75,7 @@ const renderMarkdown: BubbleListProps['roles'][string]['messageRender'] = (
h('div', { h('div', {
innerHTML: content, innerHTML: content,
style: { style: {
padding: '8px', // padding: '8px',
lineHeight: '1.6', lineHeight: '1.6',
whiteSpace: 'break-spaces', whiteSpace: 'break-spaces',
}, },
@ -81,6 +83,8 @@ const renderMarkdown: BubbleListProps['roles'][string]['messageRender'] = (
]); ]);
}; };
const conversationId = ref('');
const [PreviewDrawer, previewDrawerApi] = useVbenDrawer({ const [PreviewDrawer, previewDrawerApi] = useVbenDrawer({
// //
connectedComponent: WordPreview, connectedComponent: WordPreview,
@ -93,8 +97,15 @@ function openPreviewDrawer(
const fileData = filename.value; const fileData = filename.value;
previewDrawerApi.setState({ placement }).setData(fileData).open(); previewDrawerApi.setState({ placement }).setData(fileData).open();
} }
const inputStatus = ref<string>('');
const startFetching = async () => { const startFetching = async () => {
//
if (!projectName.value.trim()) {
inputStatus.value = 'error';
message.error('请输入项目名称');
return;
}
isFetching.value = true; isFetching.value = true;
fetchStatus.value = 'fetching'; fetchStatus.value = 'fetching';
resultItems.value.push({ resultItems.value.push({
@ -104,28 +115,13 @@ const startFetching = async () => {
}); });
try { try {
// const res = await props.runWorkflow(
// {
// appid: props.item.id,
// },
// {
// userId: '1562',
// conversationId: '',
// files: [],
// inputs: {
// declarationDoc: value.value,
// },
// },
// );
// url
const res = await props.runChatflow( const res = await props.runChatflow(
{ {
appid: 'baca08c1-e92b-4dc9-a445-3584803f54d4', appid: 'baca08c1-e92b-4dc9-a445-3584803f54d4',
}, },
{ {
userId: '1562', userId: '1562',
conversationId: '', conversationId: conversationId.value,
files: [], files: [],
inputs: { inputs: {
projectName: projectName.value, projectName: projectName.value,
@ -133,7 +129,9 @@ const startFetching = async () => {
content: value.value || '', content: value.value || '',
}, },
); );
value.value = '';
const { answer } = res; const { answer } = res;
conversationId.value = res.conversationId;
// .docx URL // .docx URL
function isDocxURL(str: string): boolean { function isDocxURL(str: string): boolean {
@ -200,36 +198,6 @@ const startFetching = async () => {
} }
fetchResult.value = `/static/${filename.value}`; fetchResult.value = `/static/${filename.value}`;
// 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) { } catch (error) {
console.error(error); console.error(error);
} }
@ -263,7 +231,7 @@ const roles: BubbleListProps['roles'] = {
h('div', { h('div', {
innerHTML: md.render(content), innerHTML: md.render(content),
style: { style: {
padding: '8px', // padding: '8px',
lineHeight: '1.6', lineHeight: '1.6',
whiteSpace: 'break-spaces', whiteSpace: 'break-spaces',
}, },
@ -306,12 +274,17 @@ const resultItems = ref<ResultItem[]>([]);
</script> </script>
<template> <template>
<div> <div style="display: flex; flex-direction: column" class="h-full">
<PreviewDrawer /> <PreviewDrawer />
<a-space class="w-full" direction="vertical"> <a-space class="w-full" direction="vertical" style="padding-top: 20px">
<a-input v-model:value="projectName" required placeholder="项目名称" /> <a-input
v-model:value="projectName"
required
placeholder="项目名称"
:status="inputStatus"
/>
</a-space> </a-space>
<div style="height: calc(67vh - 180px); margin: 20px; overflow-y: auto"> <div style="flex: 1; padding: 20px; overflow-y: auto">
<BubbleList <BubbleList
:roles="roles" :roles="roles"
:typing="true" :typing="true"
@ -319,7 +292,7 @@ const resultItems = ref<ResultItem[]>([]);
:message-render="renderMarkdown" :message-render="renderMarkdown"
/> />
</div> </div>
<Flex wrap="wrap" :gap="12"> <Flex wrap="wrap" :gap="12" class="w-full" style="align-items: flex-end">
<Button @click="insertTextToInput('生成项目摘要')">生成项目摘要</Button> <Button @click="insertTextToInput('生成项目摘要')">生成项目摘要</Button>
<Button @click="insertTextToInput('生成目前存在问题')"> <Button @click="insertTextToInput('生成目前存在问题')">
生成目前存在问题 生成目前存在问题
@ -344,7 +317,7 @@ const resultItems = ref<ResultItem[]>([]);
v-model:value="value" v-model:value="value"
:loading="isFetching" :loading="isFetching"
:disabled="isFetching" :disabled="isFetching"
:auto-size="{ minRows: 6, maxRows: 12 }" :auto-size="{ minRows: 1, maxRows: 12 }"
@submit="startFetching" @submit="startFetching"
/> />
</Card> </Card>

View File

@ -16,4 +16,6 @@ export const MageRobot = createIconifyIcon('mage:robot');
export const RiDept = createIconifyIcon('ri:door-open-line'); export const RiDept = createIconifyIcon('ri:door-open-line');
export const EosRole = createIconifyIcon('eos-icons:role-binding-outlined'); export const EosRole = createIconifyIcon('eos-icons:role-binding-outlined');
export const IconSystem = createIconifyIcon('icon-park-twotone:system'); export const IconSystem = createIconifyIcon('icon-park-twotone:system');
export const MaterPerson = createIconifyIcon('material-symbols:person');
export const IconLog = createIconifyIcon('icon-park-outline:log');
// export const MdiUser = createIconifyIcon('mdi:user'); // export const MdiUser = createIconifyIcon('mdi:user');