Merge branch 'feature/linzhijun_扣子智能体_0710' into test

This commit is contained in:
林志军
2025-08-08 10:25:13 +08:00
51 changed files with 11831 additions and 165 deletions

54
src/api/all/agent.ts Normal file
View File

@ -0,0 +1,54 @@
import Http from '@/api';
// 获取聊天智能体
export const getChatAgent = (id: number) => {
console.log('param.id', id);
return Http.get(`/v1/agent/getChatAgent/${id}`);
};
// 获取历史聊天
export const getHistoryChat = (params: any) => {
return Http.get(`/v1/agent/getConversations`, params);
};
// 获取智能体分类
export const getCategoriesMenus = () => {
return Http.get(`/v1/agent/getCategoriesMenus`);
};
export const getAgentList = (params: any) => {
return Http.get(`/v1/agent/getAgentList`, params);
};
// 获取工作流详情
export const getWorkFlowInfo = (id: number) => {
return Http.get(`/v1/agent/getWorkFlowInfo/${id}`);
};
// 执行工作流
export const executeWorkFlow = (params: any) => {
return Http.post(`/v1/agent/executeWorkFlow`, params);
};
export const delAgentMessage = (params: any) => {
return Http.post(`/v1/agent/delAgentMessage`, params);
};
// 删除工作流历史记录
export const delWorkflowHistoryApi = (id: number) => {
return Http.post(`/v1/agent/delWorkflowHistory/${id}`);
};
//获取异步执行工作流任务
export const getSyncWorkflowTaskApi = (params: any) => {
return Http.get(`/v1/agent/getSyncWorkflowTask`, params);
};
//置顶
export const topWorkflowHistoryApi = (id: number) => {
return Http.post(`/v1/agent/topWorkflowHistory/${id}`);
};
//取消指定
export const cancelTopWorkflowHistoryApi = (id: number) => {
return Http.post(`/v1/agent/cancelTopWorkflowHistory/${id}`);
};
export const getWorkflowHistoryListApi = (param: any) => {
return Http.get(`/v1/agent/getWorkflowHistoryList`, param);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/assets/svg/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5 5.41667H2.5V3.75H17.5V5.41667ZM17.5 10.8333L9.16667 10.8333V9.16667L17.5 9.16667V10.8333ZM2.5 16.25L17.5 16.25V14.5833L2.5 14.5833V16.25ZM5.88166 7.49938C6.1589 7.31999 6.52469 7.51898 6.52469 7.8492V11.9793C6.52469 12.3095 6.1589 12.5085 5.88167 12.3291L2.69031 10.264C2.43656 10.0999 2.43656 9.7286 2.69031 9.5644L5.88166 7.49938Z" fill="#737478"/>
</svg>

After

Width:  |  Height:  |  Size: 509 B

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 5.41667H17.5V3.75H2.5V5.41667ZM9.16667 10.8333L17.5 10.8333V9.16667L9.16667 9.16667V10.8333ZM17.5 16.25L2.5 16.25V14.5833L17.5 14.5833V16.25ZM2.5 7.93499C2.5 7.60478 2.86579 7.40578 3.14302 7.58517L6.33438 9.6502C6.58813 9.81439 6.58812 10.1856 6.33438 10.3498L3.14302 12.4149C2.86579 12.5943 2.5 12.3953 2.5 12.065V7.93499Z" fill="#737478"/>
</svg>

After

Width:  |  Height:  |  Size: 500 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M8.5059 0.831106C10.4088 0.831106 14.3176 1.24291 14.6778 6.00005C14.8856 9.07256 13.6609 9.81256 13.4991 10.5059C13.1986 11.3842 12.713 11.3843 12.7129 13.21V15.1739L5.7725 15.1739V13.6465C4.74907 13.7495 2.71435 12.1885 2.70218 10.8868C2.69615 10.2304 2.70079 10.0334 2.70218 10.0069C2.24176 9.87876 1.32525 9.71995 1.29984 9.01372C1.26463 8.03028 1.89527 7.32357 2.12015 7.0479C2.34503 6.77222 2.65685 6.24222 2.71488 6.03911C2.77317 5.83402 3.1725 3.14468 4.66996 2.08111C6.1682 1.01735 7.4787 0.785117 8.5059 0.831106ZM9.05863 3.20318C8.52988 3.20318 8.10062 3.63146 8.10062 4.16021C8.10063 4.2264 8.10736 4.29116 8.12015 4.35357L6.37308 5.86822C6.18682 5.7718 5.97522 5.71685 5.75101 5.71685C5.00285 5.717 4.39652 6.32412 4.39652 7.07232C4.39682 7.82026 5.00304 8.42666 5.75101 8.42681C6.32378 8.4268 6.81255 8.07073 7.01078 7.56841L10.1739 8.03228C10.308 8.51062 10.7474 8.86138 11.2686 8.86138C11.8963 8.86126 12.4053 8.35236 12.4053 7.72466C12.4053 7.09697 11.8963 6.58806 11.2686 6.58794C10.7682 6.58794 10.3433 6.91132 10.1914 7.3604L7.09476 6.9063C7.06827 6.68908 6.99181 6.48745 6.87504 6.3145L8.48148 4.92193C8.6421 5.04374 8.8415 5.11724 9.05863 5.11724C9.58722 5.1171 10.0156 4.68881 10.0157 4.16021C10.0157 3.63154 9.58726 3.20331 9.05863 3.20318Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M5.67236 1.43899C5.82421 1.44614 5.97452 1.46003 6.12256 1.48098L6.34229 1.51711L6.43994 1.54641C6.65484 1.63464 6.78543 1.86394 6.74072 2.1011C6.69562 2.33838 6.48983 2.50499 6.25732 2.50832L6.15674 2.49953L5.98193 2.47121C5.86457 2.45462 5.74526 2.4427 5.62451 2.43703L5.44287 2.43313C3.33645 2.43326 1.62842 4.14113 1.62842 6.24758L1.63623 6.49172C1.67189 7.05608 1.82941 7.58673 2.0835 8.05716L2.16943 8.21634L2.13428 8.39309L1.78369 10.1314L3.65576 9.73099L3.81006 9.69778L3.95557 9.76028C4.41203 9.95375 4.91465 10.061 5.44287 10.0611L5.63037 10.0562C7.55471 9.96281 9.10453 8.44288 9.24561 6.53176C9.26611 6.25665 9.50657 6.04968 9.78174 6.06985C10.0569 6.09036 10.2639 6.33072 10.2437 6.60598C10.0654 9.01938 8.10866 10.9376 5.6792 11.0552L5.44287 11.0611C4.83568 11.061 4.25415 10.9465 3.71826 10.7408L1.39697 11.2378C0.974828 11.328 0.598212 10.9559 0.683105 10.5327L1.12061 8.3638C0.849638 7.81142 0.67902 7.20063 0.638184 6.5552L0.628418 6.24758C0.628418 3.58885 2.78417 1.43326 5.44287 1.43313L5.67236 1.43899ZM7.68408 6.39602C7.95088 6.46634 8.11056 6.73948 8.04053 7.00637L7.97803 7.20852C7.62743 8.1978 6.6839 8.90667 5.57373 8.90677C4.3893 8.90666 3.39433 8.09977 3.10693 7.00637C3.03682 6.73942 3.1965 6.46632 3.46338 6.39602C3.73043 6.32589 4.00355 6.48542 4.07373 6.75247C4.24853 7.41727 4.85492 7.90665 5.57373 7.90677C6.29245 7.90666 6.89786 7.41715 7.07275 6.75247C7.14292 6.48545 7.41706 6.32595 7.68408 6.39602ZM9.11768 0.898947C9.22738 0.698405 9.5158 0.698391 9.62549 0.898947L10.1489 1.85598C10.1755 1.9045 10.2156 1.94468 10.2642 1.97121L11.2212 2.49465C11.4217 2.60434 11.4217 2.89276 11.2212 3.00246L10.2642 3.5259C10.2156 3.55244 10.1755 3.59261 10.1489 3.64114L9.62549 4.59817C9.5158 4.79873 9.22737 4.79873 9.11768 4.59817L8.59424 3.64114C8.5677 3.59261 8.52753 3.55244 8.479 3.5259L7.52197 3.00246C7.32141 2.89277 7.32141 2.60434 7.52197 2.49465L8.479 1.97121C8.52753 1.94468 8.5677 1.9045 8.59424 1.85598L9.11768 0.898947Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.91671 2.62492V1.45825L4.08337 1.45825V2.62492L1.45837 2.62492V3.79159H2.47921L2.47921 11.6666C2.47921 12.1498 2.87096 12.5416 3.35421 12.5416L10.6459 12.5416C11.1291 12.5416 11.5209 12.1498 11.5209 11.6666V3.79159L12.5417 3.79159V2.62492L9.91671 2.62492ZM3.64587 11.3749V3.79159L10.3542 3.79159V11.3749L3.64587 11.3749ZM5.25004 5.24992V9.62492H6.41671V5.24992H5.25004ZM7.58337 5.24992V9.62492H8.75004V5.24992H7.58337Z" fill="#F64B31"/>
</svg>

After

Width:  |  Height:  |  Size: 591 B

View File

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.10797 1.4262C8.44968 1.08449 9.0037 1.08449 9.34541 1.4262L10.9746 3.05536C11.3163 3.39707 11.3163 3.95109 10.9746 4.2928L5.01002 10.2573C4.87077 10.3966 4.68863 10.4848 4.49305 10.5077L2.72936 10.7142C2.18281 10.7782 1.71357 10.3278 1.7551 9.77908L1.89441 7.93865C1.9102 7.73001 2.00024 7.53393 2.14819 7.38598L8.10797 1.4262ZM7.67304 3.51105L8.88972 4.72773L9.94337 3.67408L8.72669 2.4574L7.67304 3.51105ZM8.06477 5.55268L6.84808 4.336L3.04959 8.1345L2.94515 9.51428L4.25672 9.36073L8.06477 5.55268ZM12.25 12.8332L1.75 12.8332V11.6665L12.25 11.6665V12.8332Z" fill="#737478"/>
</svg>

After

Width:  |  Height:  |  Size: 733 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 7L2 9L4 9V7H2ZM7 7V9L9 9V7H7ZM12 7V9L14 9V7L12 7Z" fill="#737478"/>
</svg>

After

Width:  |  Height:  |  Size: 223 B

View File

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.10963 5.99249L5.88754 6.00927L2.72457 6.25484L7.74479 11.2751L7.99037 8.1121L8.00714 7.89001L10.5716 5.32555L8.67409 3.42803L6.10963 5.99249ZM9.17397 8.42746L8.8472 12.6526C8.80683 13.1738 8.17358 13.4079 7.80384 13.0384L4.80861 10.0432L1.9355 12.9163C1.85964 12.9921 1.7363 12.9921 1.6604 12.9163L1.08404 12.3399C1.00814 12.264 1.00814 12.1407 1.08404 12.0648L3.95715 9.1917L0.961248 6.1958C0.592209 5.82608 0.826163 5.19297 1.34706 5.15243L5.57218 4.82567L7.82196 2.5759L6.9356 1.68954C6.86001 1.61362 6.86048 1.4909 6.93627 1.41511L7.51264 0.838744C7.58845 0.763153 7.71122 0.762546 7.78706 0.838073L13.1616 6.21257C13.2374 6.2884 13.2373 6.41176 13.1616 6.48767L12.5852 7.06404C12.5093 7.13993 12.386 7.13993 12.3101 7.06404L11.4237 6.17768L9.17397 8.42746Z" fill="#737478"/>
</svg>

After

Width:  |  Height:  |  Size: 895 B

View File

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.90151 8.07102C2.81319 8.07125 3.55217 8.81081 3.55217 9.72253C3.55177 10.6339 2.81294 11.373 1.90151 11.3732C0.99004 11.373 0.250403 10.634 0.25 9.72253C0.25 8.81077 0.989791 8.07119 1.90151 8.07102ZM6.00085 8.07102C6.91243 8.07137 7.65151 8.81088 7.65151 9.72253C7.65111 10.6338 6.91218 11.3728 6.00085 11.3732C5.08923 11.3732 4.34974 10.6341 4.34934 9.72253C4.34934 8.81066 5.08898 8.07102 6.00085 8.07102ZM10.0993 8.07102C11.0109 8.07142 11.75 8.81091 11.75 9.72253C11.7496 10.6338 11.0106 11.3728 10.0993 11.3732C9.18772 11.3732 8.44823 10.6341 8.44783 9.72253C8.44783 8.81066 9.18747 8.07102 10.0993 8.07102ZM1.90151 8.945C1.47248 8.94517 1.12398 9.29346 1.12398 9.72253C1.12438 10.1513 1.47272 10.499 1.90151 10.4992C2.33025 10.499 2.67779 10.1512 2.67819 9.72253C2.67819 9.29349 2.3305 8.94523 1.90151 8.945ZM6.00085 8.945C5.57167 8.945 5.22332 9.29335 5.22332 9.72253C5.22372 10.1514 5.57192 10.4992 6.00085 10.4992C6.4295 10.4989 6.77713 10.1512 6.77753 9.72253C6.77753 9.29356 6.42974 8.94534 6.00085 8.945ZM10.0993 8.945C9.67015 8.945 9.32181 9.29335 9.32181 9.72253C9.32221 10.1514 9.6704 10.4992 10.0993 10.4992C10.5279 10.4988 10.8756 10.1511 10.876 9.72253C10.876 9.2936 10.5282 8.9454 10.0993 8.945ZM6.3414 4.11848C6.38942 4.11866 6.4283 4.15751 6.42845 4.20554V5.31935H8.09448C9.30104 5.31952 10.2794 6.29769 10.2794 7.5043V7.67756H9.40545V7.5043C9.40545 6.78038 8.81836 6.1935 8.09448 6.19333H6.42845V7.82948C6.42845 7.87764 6.38952 7.91722 6.3414 7.91739H5.65946C5.61119 7.91739 5.5724 7.87775 5.5724 7.82948V6.19333H3.90638C3.18235 6.19333 2.59541 6.78027 2.59541 7.5043V7.67756H1.72143V7.5043C1.72143 6.29758 2.69966 5.31935 3.90638 5.31935H5.5724V4.20554C5.57255 4.1574 5.61128 4.11848 5.65946 4.11848H6.3414ZM6.00085 0.626831C6.91243 0.627177 7.65151 1.36669 7.65151 2.27834C7.65114 3.18968 6.9122 3.92866 6.00085 3.92901C5.08921 3.92901 4.34971 3.1899 4.34934 2.27834C4.34934 1.36647 5.08898 0.626831 6.00085 0.626831ZM6.00085 1.50081C5.57167 1.50081 5.22332 1.84916 5.22332 2.27834C5.22369 2.70721 5.5719 3.05503 6.00085 3.05503C6.42951 3.05468 6.77716 2.707 6.77753 2.27834C6.77753 1.84937 6.42974 1.50116 6.00085 1.50081Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,6 +1,10 @@
<template>
<div class="right-wrap">
<div class="relative mr-12px" @click="setUnread">
<!-- 灵机空间入口 -->
<div class="agent-entry" :class="isAgentRoute ? 'agent' : ''" @click="handleAgentClick"></div>
<!-- 任务中心 -->
<div class="relative mx-16px" @click="setUnread">
<SvgIcon
name="svg-taskCenter"
size="16"
@ -10,6 +14,7 @@
<div class="w-4px h-4px rounded-50% bg-#F64B31 absolute top-1px right-1px" v-if="hasUnreadInfo"></div>
</div>
<!-- 头像设置 -->
<a-dropdown trigger="click" class="layout-avatar-dropdown">
<a-avatar class="cursor-pointer" :size="32">
<img alt="avatar" src="@/assets/avatar.svg" />
@ -83,9 +88,17 @@ import icon1 from '@/assets/option.svg';
import icon2 from '@/assets/exit.svg';
import icon3 from '@/assets/change.svg';
const props = defineProps({
isAgentRoute: {
type: Boolean,
default: false,
},
});
const enterpriseStore = useEnterpriseStore();
const userStore = useUserStore();
const sideBarStore = useSidebarStore();
const route = useRoute();
const hasUnreadInfo = computed(() => sideBarStore.unreadInfo.length);
@ -117,15 +130,13 @@ const setUnread = () => {
sideBarStore.removeTaskUnreadInfo();
}
};
const handleAgentClick = () => {
router.push({ name: props.isAgentRoute ? 'Home' : 'AgentIndex' });
};
</script>
<style scoped lang="scss">
.right-wrap {
display: flex;
padding-right: 20px;
list-style: none;
align-items: center;
}
@import './style.scss';
</style>
<style lang="scss">
.layout-avatar-dropdown,

View File

@ -0,0 +1,27 @@
.right-wrap {
display: flex;
padding-right: 20px;
list-style: none;
align-items: center;
.agent-entry {
width: 100px;
height: 32px;
background: url('@/assets/img/agent/icon-entry.png') 100% 100%;
background-size: cover;
transition: all 0.3s;
cursor: pointer;
&:hover {
background: url('@/assets/img/agent/icon-entry-hover.png') 100% 100%;
background-size: cover;
}
&.agent {
background: url('@/assets/img/agent/icon-home.png') 100% 100%;
background-size: cover;
cursor: pointer;
&:hover {
background: url('@/assets/img/agent/icon-home-hover.png') 100% 100%;
background-size: cover;
}
}
}
}

View File

@ -6,9 +6,9 @@
</a-space>
</div>
<div class="flex-1">
<NavbarMenu />
<NavbarMenu v-if="!isAgentRoute"/>
</div>
<RightSide />
<RightSide :isAgentRoute="isAgentRoute" />
</div>
</template>
@ -17,6 +17,12 @@ import NavbarMenu from './components/navbar-menu';
import RightSide from './components/right-side';
import router from '@/router';
const route = useRoute();
const isAgentRoute = computed(() => {
return route.meta?.isAgentRoute;
});
</script>
<style scoped lang="scss">
.navbar-wrap {

View File

@ -0,0 +1,141 @@
<template>
<a-tooltip :disabled="isShowBtn || (!isShowBtn && disabled)" :placement="props.placement">
<template #content>
<div :style="contentStyle" class="tip-content">{{ props.context }}</div>
</template>
<div class="overflow-hidden">
<div v-bind="$attrs" ref="Text" :class="`${isShow ? '' : `line-${props.line}`} `" class="overflow-text">
{{ props.context }}
</div>
<div
v-if="isShowBtn && !disabled"
class="color-#8C8C8C flex items-center cursor-pointer mt-2px"
@click="
() => {
isShow = !isShow;
}
"
>
{{ isShow ? '收起' : '展开' }}
<icon-up size="16" :class="{ active: isShow }" class="ml-2px color-#8C8C8C" />
</div>
</div>
</a-tooltip>
</template>
<script setup>
import { ref, reactive, toRefs, onBeforeMount, onMounted, watchEffect, computed, watch, nextTick, defineProps } from 'vue';
import elementResizeDetectorMaker from 'element-resize-detector';
const props = defineProps({
context: {
type: String,
default: '',
},
placement: {
type: String,
default: 'bottom',
},
line: {
type: Number,
default: 1,
},
maxHeight: {
type: [String, Number],
default: '',
},
maxWidth: {
type: [String, Number],
default: '',
},
isShowBtn: {
type: Boolean,
default: false,
},
});
const data = reactive({});
const isShow = ref(false);
const contentStyle = computed(() => {
let style = {
'max-height': props.maxHeight + 'px',
'max-width': props.maxWidth + 'px',
overflow: 'auto',
padding: '1px 0',
};
return props.maxHeight || props.maxWidth ? style : {};
});
const disabled = ref(true);
const Text = ref(null);
const textWidth = ref();
watch(
[() => props.context, textWidth],
async () => {
if (props.context) {
await nextTick();
nextTick(() => {
if (props.line < 2) {
if (Text.value?.clientWidth < Text.value?.scrollWidth) {
disabled.value = false;
} else {
disabled.value = true;
}
} else {
if (Text.value?.clientHeight < Text.value?.scrollHeight) {
disabled.value = false;
} else {
disabled.value = true;
}
}
});
}
},
{
deep: true,
immediate: true,
},
);
onBeforeMount(() => {});
onMounted(async () => {
await nextTick();
const erd = elementResizeDetectorMaker();
if (Text.value) {
erd.listenTo(Text.value, () => {
const _width = Text.value.getBoundingClientRect().width;
textWidth.value = _width;
});
}
});
watchEffect(() => {});
defineExpose({
...toRefs(data),
});
</script>
<style scoped lang="scss">
.overflow-text {
display: inline-block;
font-family: $font-family-regular;
width: 100%;
vertical-align: middle;
font-style: normal;
box-sizing: border-box;
white-space: pre-line;
&.line-1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.line-2 {
@include multi-ellipsis(2);
}
&.line-3 {
@include multi-ellipsis(3);
}
}
.tip-content {
white-space: pre-line;
}
.active {
transform: rotate(-180deg);
}
</style>

View File

@ -0,0 +1,137 @@
<template>
<a-upload
:custom-request="customRequest"
action="/"
:limit="limit"
:fileList="fileList"
@change="onChange"
@success="handleSuccess"
@error="handleError"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import { fetchImageUploadFile, fetchUploadFile } from '@/api/all';
import axios from 'axios';
const fileList = ref([]);
const props = defineProps({
modelValue: {
type: [Array, String],
default: '',
},
limit: {
type: Number,
default: 0, // 0 表示不限制
},
});
const emit = defineEmits(['update:modelValue']);
const handleSuccess = (fileItem) => {
let response = fileItem.response;
response = JSON.parse(response);
if (response && response.data.file_url) {
if (props.limit === 1) {
emit('update:modelValue', response.data.file_url);
} else {
emit('update:modelValue', [...props.modelValue, response.data.file_url]);
}
}
};
watch(
() => props.modelValue,
async (value) => {
console.log(value, 'value');
if (value) {
fileList.value =
props.limit == 1
? [
{
name: '',
url: props.modelValue as string,
},
]
: (props.modelValue as string[]).map((item) => {
return {
name: '',
url: item,
};
});
} else {
fileList.value = [];
}
},
{ once: true },
);
let previousFileListLength = 0;
//删除图片
const onChange = (fileList) => {
if (fileList.length < previousFileListLength) {
if (props.limit === 1) {
if (fileList.length === 0) {
emit('update:modelValue', '');
}
} else {
if (fileList.length === 0) {
emit('update:modelValue', []);
} else {
let image_data = fileList.map((item) => item.url);
emit('update:modelValue', image_data);
}
}
}
previousFileListLength = fileList.length;
};
const beforeUpload = (file, files) => {
if (props.limit > 0 && files.length >= props.limit) {
Message.warning(`最多只能上传 ${props.limit} 张图片`);
return false; // 阻止上传
}
return true;
};
const handleError = (error) => {
Message.error('上传失败');
console.error(error);
};
const customRequest = async (option) => {
const { onProgress, onError, onSuccess, fileItem, name } = option;
try {
// 1. 获取预签名上传URL
const response = await fetchUploadFile({ suffix: getFileExtension(fileItem.file.name) });
const preSignedUrl = response?.data?.upload_url;
if (!preSignedUrl) {
throw new Error('未能获取有效的预签名上传地址');
}
console.log('preSignedUrl', preSignedUrl);
// 2. 使用预签名URL上传文件
const blob = new Blob([fileItem.file], { type: fileItem.file.type });
await axios.put(preSignedUrl, blob, {
headers: { 'Content-Type': fileItem.file.type },
});
onSuccess(JSON.stringify(response));
} catch (error) {
onError(error);
}
};
function getFileExtension(filename: string): string {
const match = filename.match(/\.([^.]+)$/);
return match ? match[1].toLowerCase() : '';
}
</script>
<style scoped>
/* 添加一些样式 */
</style>

View File

@ -0,0 +1,51 @@
import { getImageMainColor } from '@/utils/tools';
const DEFAULT_COLOR = '#E6E6E8';
const pendingRequests = new Map<HTMLElement, Promise<string | void>>();
const imageMainColorDirective = {
mounted(el: HTMLElement, binding: any) {
updateColor(el, binding.value);
},
updated(el: HTMLElement, binding: any) {
if (binding.value !== binding.oldValue) {
updateColor(el, binding.value);
}
},
beforeUnmount(el: HTMLElement) {
pendingRequests.delete(el);
}
};
function updateColor(el: HTMLElement, imageUrl: string) {
if (!imageUrl) {
el.style.backgroundColor = DEFAULT_COLOR;
return;
}
// 创建新的请求
const request = getImageMainColor(imageUrl)
.then(color => {
if (pendingRequests.get(el) === request) {
el.style.backgroundColor = color;
pendingRequests.delete(el);
}
return color;
})
.catch(error => {
console.error('获取图片主色调失败:', error);
el.style.backgroundColor = DEFAULT_COLOR;
pendingRequests.delete(el);
});
pendingRequests.set(el, request);
}
// 获取图片主色调指令
export default {
install(app: any) {
app.directive('image-main-color', imageMainColorDirective);
}
};

View File

@ -0,0 +1,3 @@
import getImageMainColor from './getImageMainColor';
export { getImageMainColor };

View File

@ -61,7 +61,7 @@ provide('toggleDrawerMenu', () => {
</script>
<template>
<a-layout :class="['layout', { mobile: appStore.hideMenu }]">
<a-layout :class="['layout', { mobile: appStore.hideMenu }]" class="h-100vh flex flex-col w-full">
<JoinModal v-model:visible="joinEnterpriseVisible" ref="joinModalRef" />
<div v-if="navbar" class="layout-navbar">
<base-navbar />
@ -98,7 +98,7 @@ provide('toggleDrawerMenu', () => {
<a-layout class="layout-content" :style="paddingStyle">
<base-tab-bar v-if="appStore.tabBar" />
<a-layout-content class="px-5 py-5">
<base-breadcrumb />
<!-- <base-breadcrumb /> -->
<layout-page />
</a-layout-content>
</a-layout>
@ -111,15 +111,11 @@ provide('toggleDrawerMenu', () => {
$nav-size-height: 72px;
$layout-max-width: 1100px;
.layout {
width: 100%;
height: 100%;
}
.layout-navbar {
position: fixed;
top: 0;
left: 0;
z-index: 100;
z-index: 1000;
width: 100%;
height: $nav-size-height;
}
@ -168,9 +164,9 @@ $layout-max-width: 1100px;
}
.layout-content {
min-width: 1366px;
min-height: 100vh;
height: 100%;
overflow-y: hidden;
background-color: var(--color-fill-2);
background-color: $color-background;
transition: padding 0.2s cubic-bezier(0.34, 0.69, 0.1, 1);
}
</style>

View File

@ -8,6 +8,7 @@ const route = useRoute();
const routerKey = computed(() => {
return route.path + Math.random();
});
const hideFooter = computed(() => route.meta?.hideFooter);
/*** - end */
</script>
@ -19,7 +20,7 @@ const routerKey = computed(() => {
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</transition>
<view class="footer">
<view class="footer" v-if="!hideFooter">
<view>闽公网安备 352018502850842 闽ICP备20250520582号 © 2025小题科技All Rights Reserved.</view>
<view>* 数据通过公开渠道获取灵机进行统计分析</view>
</view>

View File

@ -5,9 +5,10 @@
import App from './App.vue';
import router from './router';
import store from './stores';
import * as directives from '@/directives';
import NoData from '@/components/no-data';
import SvgIcon from "@/components/svg-icon";
import SvgIcon from '@/components/svg-icon';
import '@/api/index';
import '@arco-design/web-vue/dist/arco.css'; // Arco 默认样式
@ -15,7 +16,7 @@ import './core';
import 'normalize.css';
import 'uno.css';
import 'virtual:svg-icons-register'
import 'virtual:svg-icons-register';
// import '@/styles/vars.css'; // 优先加载
@ -26,4 +27,7 @@ app.component('SvgIcon', SvgIcon);
app.use(store);
app.use(router);
Object.keys(directives).forEach((k) => app.use(directives[k])); // 注册指令
app.mount('#app');

View File

@ -26,4 +26,5 @@ export const MENU_GROUP_IDS = {
MANAGEMENT_ID: -1, // 管理中心
PROPERTY_ID: 10, // 资产营销平台
WORK_BENCH_ID: -99, // 工作台
AGENT: 2, // 智能体
};

View File

@ -8,6 +8,7 @@
import type { Router } from 'vue-router';
import { setRouteEmitter } from '@/utils/route-listener';
import setupUserLoginInfoGuard from './userLoginInfo';
import { MENU_GROUP_IDS } from '@/router/constants';
// import setupPermissionGuard from './permission';
function setupPageGuard(router: Router) {
@ -17,7 +18,9 @@ function setupPageGuard(router: Router) {
});
}
export default function createRouteGuard(router: Router) {
setupPageGuard(router);
setupUserLoginInfoGuard(router);
// setupPermissionGuard(router);

View File

@ -35,26 +35,7 @@ export const router = createRouter({
id: MENU_GROUP_IDS.WORK_BENCH_ID,
},
},
{
path: '/permission',
name: 'permission',
component: () => import('@/views/components/permission/choose-enterprise.vue'),
meta: {
requiresAuth: false,
requireLogin: true,
},
},
// {
// path: '/auth',
// name: 'auth',
// component: () => import('@/views/components/permission/auth.vue'),
// meta: {
// requiresAuth: false,
// requireLogin: true,
// },
// },
...appRoutes,
// REDIRECT_MAIN,
NOT_FOUND_ROUTE,
],
scrollBehavior() {

View File

@ -0,0 +1,65 @@
import type { AppRouteRecordRaw } from '../types';
import { MENU_GROUP_IDS } from '@/router/constants';
import IconRepository from '@/assets/svg/svg-agent.svg';
const COMPONENTS: AppRouteRecordRaw[] = [
{
path: '/agent',
name: 'Agent',
redirect: 'agent/index',
meta: {
locale: '灵机ai',
icon: IconRepository,
requiresAuth: true,
requireLogin: true,
roles: ['*'],
id: MENU_GROUP_IDS.AGENT,
},
children: [
{
path: 'index',
name: 'AgentIndex',
component: () => import('@/views/agent/index'),
meta: {
locale:'智能体应用',
requiresAuth: false,
requireLogin: true,
hideFooter: true,
isAgentRoute: true
},
},
{
path: 'chat',
name: 'AgentChat',
component: () => import('@/views/agent/chat'),
meta: {
hideSidebar: true,
requiresAuth: false,
requireLogin: true,
hideFooter: true,
id: MENU_GROUP_IDS.AGENT,
isAgentRoute: true,
hideInMenu: true,
},
},
{
path: 'workFlow',
name: 'AgentWorkFlow',
component: () => import('@/views/agent/work-flow'),
meta: {
hideSidebar: true,
requiresAuth: false,
requireLogin: true,
hideFooter: true,
id: MENU_GROUP_IDS.AGENT,
isAgentRoute: true,
hideInMenu: true,
},
},
],
},
];
export default COMPONENTS;

View File

@ -1,7 +1,3 @@
/*
* @Author: RenXiaoDong
* @Date: 2025-06-23 06:39:28
*/
import { IconBookmark } from '@arco-design/web-vue/es/icon';
import type { AppRouteRecordRaw } from '../types';
import { MENU_GROUP_IDS } from '@/router/constants';

View File

@ -1,7 +1,3 @@
/*
* @Author: RenXiaoDong
* @Date: 2025-06-23 04:29:03
*/
import { IconBookmark } from '@arco-design/web-vue/es/icon';
import type { AppRouteRecordRaw } from '../types';
import { MENU_GROUP_IDS } from '@/router/constants';

View File

@ -14,6 +14,7 @@ declare module 'vue-router' {
noAffix?: boolean; // if set true, the tag will not affix in the tab-bar
ignoreCache?: boolean; // if set true, the page will not be cached
hideSidebar?: boolean;
isAgentRoute?:boolean;
requireLogin?: boolean; // 是否需要登陆才能访问
}
}

View File

@ -11,7 +11,7 @@ $font-family-manrope-medium: 'Manrope-Medium';
$font-family-manrope-bold: 'Manrope-Bold';
$font-family-manrope-semiBold: 'Manrope-SemiBold';
$color-background: #f9f9f9;
$color-background: #F7F8FA;
$color-primary: #6d4cfe; // 常规
$color-primary-5: #8A70FE; // hover

View File

@ -109,4 +109,174 @@ export function downloadByUrl(url: string, filename?: string) {
export function genRandomId() {
return `id_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
}
}
export function getImageMainColor(imageUrl: string): Promise<string> {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'Anonymous'; // 处理跨域图片
img.src = imageUrl;
img.onload = () => {
// 创建画布缩小图片尺寸以提高性能
const canvas = document.createElement('canvas');
const maxDimension = 100; // 最大尺寸为100px
let width = img.width;
let height = img.height;
// 按比例缩小图片
if (width > height) {
if (width > maxDimension) {
height *= maxDimension / width;
width = maxDimension;
}
} else {
if (height > maxDimension) {
width *= maxDimension / height;
height = maxDimension;
}
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (!ctx) {
reject(new Error('Canvas context not available'));
return;
}
// 绘制缩小后的图片
ctx.drawImage(img, 0, 0, width, height);
// 获取图片数据
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
// 使用中位数切分法提取主色调
const colorGroups = medianCut(data, 8); // 分成8组
// 找出最大的颜色组
let maxGroup = colorGroups[0];
for (let i = 1; i < colorGroups.length; i++) {
if (colorGroups[i].count > maxGroup.count) {
maxGroup = colorGroups[i];
}
}
// 计算组内平均颜色
const avgColor = {
r: Math.round(maxGroup.sumR / maxGroup.count),
g: Math.round(maxGroup.sumG / maxGroup.count),
b: Math.round(maxGroup.sumB / maxGroup.count)
};
resolve(`rgb(${avgColor.r},${avgColor.g},${avgColor.b})`);
};
img.onerror = () => {
reject(new Error('Failed to load image'));
};
});
}
/**
* 中位数切分法进行色彩量化
* @param data 图像数据
* @param levels 切分级别
* @returns 颜色组
*/
function medianCut(data: Uint8ClampedArray, levels: number): any[] {
const colors = [];
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const a = data[i + 3];
// 跳过透明像素
if (a < 128) continue;
colors.push({
r, g, b,
count: 1,
sumR: r, sumG: g, sumB: b
});
}
// 如果没有颜色数据,返回默认白色
if (colors.length === 0) {
return [{
count: 1,
sumR: 255, sumG: 255, sumB: 255
}];
}
// 开始中位数切分
let colorGroups = [{
colors,
count: colors.length,
sumR: colors.reduce((sum, c) => sum + c.r, 0),
sumG: colors.reduce((sum, c) => sum + c.g, 0),
sumB: colors.reduce((sum, c) => sum + c.b, 0)
}];
for (let i = 0; i < levels; i++) {
const newGroups = [];
for (const group of colorGroups) {
if (group.colors.length <= 1) {
newGroups.push(group);
continue;
}
// 找出颜色范围最大的通道
const rMin = Math.min(...group.colors.map(c => c.r));
const rMax = Math.max(...group.colors.map(c => c.r));
const gMin = Math.min(...group.colors.map(c => c.g));
const gMax = Math.max(...group.colors.map(c => c.g));
const bMin = Math.min(...group.colors.map(c => c.b));
const bMax = Math.max(...group.colors.map(c => c.b));
const rRange = rMax - rMin;
const gRange = gMax - gMin;
const bRange = bMax - bMin;
let sortChannel = 'r';
if (gRange > rRange && gRange > bRange) {
sortChannel = 'g';
} else if (bRange > rRange && bRange > gRange) {
sortChannel = 'b';
}
// 按最大范围通道排序
group.colors.sort((a, b) => a[sortChannel] - b[sortChannel]);
// 切分中位数
const mid = Math.floor(group.colors.length / 2);
const group1 = group.colors.slice(0, mid);
const group2 = group.colors.slice(mid);
// 添加新组
newGroups.push({
colors: group1,
count: group1.length,
sumR: group1.reduce((sum, c) => sum + c.r, 0),
sumG: group1.reduce((sum, c) => sum + c.g, 0),
sumB: group1.reduce((sum, c) => sum + c.b, 0)
});
newGroups.push({
colors: group2,
count: group2.length,
sumR: group2.reduce((sum, c) => sum + c.r, 0),
sumG: group2.reduce((sum, c) => sum + c.g, 0),
sumB: group2.reduce((sum, c) => sum + c.b, 0)
});
}
colorGroups = newGroups;
}
return colorGroups;
}

View File

@ -0,0 +1,11 @@
export enum AGENT_TYPE {
AGENT = 1, // 智能体
WORKFLLOW = 2, // 工作流
}
//工作流异步执行状态
export enum WORKEXECUTE_STATUS {
SUCCESS = 'Success', // 执行成功
RUNNING = 'Running', // 执行中
FAIL = 'Fail', // 执行失败
}

View File

@ -0,0 +1,53 @@
<template>
<div class="agent-card">
<div class="header-section">
<div class="image-container">
<img :src="cozeInfo.icon_url" alt="" />
</div>
</div>
<div class="info-section">
<div class="title-group">
<a-tooltip :content="cozeInfo.name">
<div class="title">{{ cozeInfo.name }}</div>
</a-tooltip>
<div class="tag">
<div>
<img class="status-icon" :src="chatbotIcon" />
</div>
<div class="text">对话式</div>
</div>
</div>
<div class="usage-info">
<a-space>
<span class="count">{{ cozeInfo.views }}</span>
</a-space>
<a-space>
<span class="label"> 次使用 </span>
</a-space>
</div>
</div>
<div class="description-section">
<div class="description">
{{ cozeInfo.description }}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue';
import chatbotIcon from '@/assets/svg/chatbot.svg';
const props = defineProps({
cozeInfo: {
type: Object as () => any,
default: () => ({}),
},
});
onMounted(() => {});
</script>
<style scoped>
@import './history.scss';
</style>

View File

@ -0,0 +1,241 @@
.agent-card {
width: 100%;
height: 100%;
background: var(--BG-100, #F7F8FA);
overflow: hidden;
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
.header-section {
align-self: stretch;
height: 300px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
.image-container {
height: 400px;
overflow: hidden;
border-radius: 10px;
}
.image-container img {
width: 100%;
height: auto;
padding: 24px;
aspect-ratio: 1 / 1;
object-fit: cover;
border-radius: 10px;
}
}
.info-section {
align-self: stretch;
padding: 10px 10px 0 12px;
display: flex;
justify-content: space-between;
align-items: flex-start;
.title-group {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 4px;
.title {
color: var(--Text-1, #211F24);
font-size: 18px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 26px;
padding: 10px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 150px;
flex-shrink: 0;
}
@media (max-width: 768px) {
.title {
font-size: 16px;
padding: 8px;
max-width: 100px;
}
}
.tag {
height: 20px;
padding: 0 8px;
background: var(--Brand-1, #F0EDFF);
border-radius: 2px;
display: flex;
justify-content: flex-start;
align-items: center;
gap: 4px;
.icon {
width: 12px;
height: 12px;
position: relative;
&::before {
content: '';
width: 10.74px;
height: 10.50px;
left: 0.63px;
top: 0.75px;
position: absolute;
background: var(--Brand-6, #6D4CFE);
}
}
.text {
color: var(--Brand-6, #6D4CFE);
font-size: 12px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 20px;
}
}
}
.usage-info {
padding: 12px;
display: flex;
align-items: center;
gap: 8px;
.count {
color: var(--Text-2, #3C4043);
font-size: 16px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 24px;
white-space: nowrap;
}
.label {
color: var(--Text-3, #737478);
font-size: 12px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 20px;
white-space: nowrap;
}
@media (max-width: 768px) {
flex-direction: column;
align-items: flex-start;
gap: 4px;
.count {
font-size: 14px;
}
.label {
font-size: 10px;
}
}
}
}
.description-section {
align-self: stretch;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
gap: 10px;
.description {
align-self: stretch;
color: var(--Text-2, #3C4043);
font-size: 14px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 22px;
padding: 24px;
}
}
.history-section {
align-self: stretch;
flex: 1 1 0;
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
.history-title {
align-self: stretch;
height: 40px;
display: flex;
justify-content: flex-start;
align-items: center;
gap: 10px;
.text {
color: var(--Text-3, #737478);
font-size: 14px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 22px;
}
}
.history-list {
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
.history-item {
align-self: stretch;
height: 40px;
padding: 8px;
border-radius: 8px;
display: flex;
justify-content: flex-start;
align-items: center;
gap: 10px;
.item-text {
flex: 1 1 0;
color: var(--Text-1, #211F24);
font-size: 14px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 22px;
}
}
}
.scroll-indicator {
width: 8px;
height: 240px;
padding: 8px 1px;
position: absolute;
left: 362px;
top: 40px;
display: flex;
justify-content: flex-start;
align-items: flex-start;
gap: 10px;
.indicator-bar {
flex: 1 1 0;
height: 80px;
background: var(--BG-600, #939499);
border-radius: 4px;
}
}
}
}

View File

@ -0,0 +1,205 @@
<template>
<div class="px-4px h-full flex flex-col overflow-y-auto">
<div class="back-wap cursor-pointer mb-17px mt--3px !w-fit" @click="goChatIndex">
<icon-left size="16" class="color-#737478 mr-4px" />
<span class="cs">返回空间</span>
</div>
<div class="workflow-container">
<div class="left-wap mr-24px" v-if="isCollapsed == false">
<div class="w-full w-100% mb-15px h-160px rounded-8px" v-image-main-color="cozeInfo.image_url">
<img v-if="cozeInfo?.image_url" :src="cozeInfo?.image_url" class="w-full h-full object-contain" />
</div>
<div class="content mb-15px">
<div class="title-body">
<div class="text mr-4px">{{ cozeInfo?.name }}</div>
<div class="tag-body">
<div class="">
<SvgIcon size="12" name="svg-chatbot" class="color-#6D4CFE" />
</div>
<div class="text">对话式</div>
</div>
</div>
<div class="use-body flex items-center">
<div class="num mr-2px">{{ formatNumberShow({ value: cozeInfo?.views, showExactValue: true }) }}</div>
<div class="text">次使用</div>
</div>
</div>
<div class="description">
<div class="text">
{{ cozeInfo?.description }}
</div>
</div>
</div>
<div class="right-wap">
<div class="header">
<div class="body">
<div class="">
<div class="toggle-btn cursor-pointer" @click="toggleCollapse">
<a-tooltip :content="isCollapsed ? '展开' : '折叠'">
<img class="status-icon" :src="isCollapsed ? menuUnfold : menuFold" />
</a-tooltip>
</div>
</div>
</div>
</div>
<div class="coze-content" id="coze-chat-container"></div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { getChatAgent } from '@/api/all/agent';
import { useRouter } from 'vue-router';
import menuFold from '@/assets/svg/menu-fold.svg';
import menuUnfold from '@/assets/svg/menu-unfold.svg';
import { formatNumberShow } from '@/utils/tools';
const router = useRouter();
// 存储认证令牌
const authToken = ref('');
const isCollapsed = ref(false);
// 切换折叠状态
const toggleCollapse = () => {
isCollapsed.value = !isCollapsed.value;
};
// 模拟从API获取token
const fetchToken = async () => {
// 实际开发中应替换为真实的 API 请求
return new Promise((resolve) => {
setTimeout(() => {
resolve('pat_' + Math.random().toString(36).substring(2, 15));
}, 500);
});
};
// 刷新token
const refreshToken = async () => {
authToken.value = await fetchToken();
};
const goChatIndex = async () => {
router.push({
path: '/agent/index',
});
};
// 动态加载脚本函数
const loadScript = (src) =>
new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
const route = useRoute();
const id = route.query.id;
const query = reactive({
id: id,
});
const cozeInfo = ref({
title: '',
description: '',
image_url: '',
bot_id: '',
auth: {},
});
let cozeWebSDK = null;
const initChat = async () => {
const { code, data } = await getChatAgent(query.id);
if (code !== 200) {
return false;
}
Object.assign(cozeInfo.value, data.info);
await loadScript('https://lf-cdn.coze.cn/obj/unpkg/flow-platform/chat-app-sdk/1.2.0-beta.17/libs/cn/index.js');
let cozeConfig = await cozeWebSdkConfig(data.info.bot_id, data.info.name, data.info.auth, data.info.user_info);
cozeWebSDK = cozeWebSDK = new CozeWebSDK.WebChatClient(cozeConfig);
showChatPage();
};
const cozeWebSdkConfig = (botId, name, auth, userInfo) => {
auth.onRefreshToken = function () {
return '';
};
let config = {
config: {
botId: botId,
},
ui: {
chatBot: {
el: document.getElementById('coze-chat-container'),
width: '100%',
height: '100%',
title: name,
isNeedFunctionCallMessage: true,
isNeedAudio: false,
feedback: {
isNeedFeedback: true,
feedbackPanel: {
title: '您对这个回答有什么看法?请告诉我们',
placeholder: '请详细描述您的问题...',
tags: [
{
label: '信息不正确',
},
{
label: '涉及敏感信息',
isNeedDetail: true,
},
],
},
},
},
base: {
icon: '',
zIndex: 100,
lang: 'zh-CN',
},
footer: {
expressionText: '内容由AI生成无法确保真实准确仅供参考。',
},
header: {
isShow: false,
isNeedClose: false,
},
conversations: {
isNeed: true,
isNeedAddNewConversation: true,
isNeedQuote: true,
},
},
auth: auth,
userInfo: userInfo,
header: {
isShow: false,
isNeedClose: false,
},
};
return config;
};
const showChatPage = () => {
cozeWebSDK.showChatBot();
};
onMounted(() => {
initChat();
});
onUnmounted(() => {
cozeWebSDK.destroy();
});
</script>
<style scoped lang="scss">
@import './style.scss';
</style>

View File

@ -0,0 +1,286 @@
.cs {
color: #3C4043;
font-family: $font-family-regular;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
}
.back-wap {
width: 100%;
justify-content: flex-start;
align-items: center;
display: inline-flex;
}
.workflow-container {
width: 100%;
height: 100%;
justify-content: flex-start;
align-items: center;
display: inline-flex;
.left-wap {
width: 360px;
align-self: stretch;
overflow: hidden;
border-radius: 8px;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
display: inline-flex;
background: var(--BG-100, #F7F8FA);
// .header {
// align-self: stretch;
// height: 160px;
// flex-direction: column;
// justify-content: center;
// align-items: center;
// gap: 10px;
// display: flex;
// .image-body {
// align-self: stretch;
// flex: 1 1 0;
// position: relative;
// background: #FFEDED;
// overflow: hidden;
// border-radius: 8px;
// img {
// width: 408.90px;
// height: 218px;
// left: -24.45px;
// top: -29px;
// position: absolute
// }
// }
// }
.content {
align-self: stretch;
justify-content: space-between;
align-items: flex-start;
display: inline-flex;
.title-body {
justify-content: flex-start;
align-items: center;
// gap: 4px;
display: flex;
.text {
justify-content: center;
display: flex;
flex-direction: column;
color: var(--Text-1, #211F24);
font-size: 18px;
font-family: $font-family-medium;
font-weight: 400;
line-height: 26px;
word-wrap: break-word;
}
.tag-body {
height: 20px;
padding-left: 8px;
padding-right: 8px;
background: var(--Functional-Red-1, #F0EDFF);
overflow: hidden;
border-radius: 2px;
justify-content: flex-start;
align-items: center;
gap: 4px;
display: flex;
.text {
color: var(--Functional-Red-6, #6D4CFE);
font-size: 12px;
font-family: $font-family-medium;
font-weight: 400;
line-height: 20px;
word-wrap: break-word
}
}
}
.use-body {
.num {
color: var(--Text-2, #3C4043);
font-size: 16px;
font-family: $font-family-manrope-medium;
font-weight: 400;
line-height: 24px;
word-wrap: break-word
}
.text {
color: var(--Text-3, #737478);
font-size: 12px;
font-family: $font-family-regular;
font-weight: 400;
line-height: 20px;
word-wrap: break-word;
}
}
}
.description {
align-self: stretch;
overflow: hidden;
flex-direction: column;
justify-content: flex-start;
align-items: center;
gap: 10px;
display: flex;
.text {
align-self: stretch;
color: var(--Text-2, #3C4043);
font-size: 14px;
font-family: $font-family-regular;
font-weight: 400;
line-height: 22px;
word-wrap: break-word;
}
}
.out-line {
align-self: stretch;
height: 40px;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 10px;
display: flex;
.out-line-div {
align-self: stretch;
height: 0px;
outline: 1px var(--Border-2, #E6E6E8) solid;
outline-offset: -0.50px;
}
}
.history {
align-self: stretch;
flex: 1 1 0;
position: relative;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
display: flex;
.section {
align-self: stretch;
height: 40px;
justify-content: flex-start;
align-items: center;
gap: 10px;
display: inline-flex;
.text {
color: var(--Text-3, #737478);
font-size: 14px;
font-family: Alibaba PuHuiTi;
font-weight: 400;
line-height: 22px;
word-wrap: break-word
}
}
.history-item {
align-self: stretch;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
display: flex;
.item-body {
align-self: stretch;
height: 40px;
padding: 8px;
border-radius: 8px;
justify-content: flex-start;
align-items: center;
gap: 10px;
display: inline-flex;
.text {
flex: 1 1 0;
color: var(--Text-1, #211F24);
font-size: 14px;
font-family: Alibaba PuHuiTi;
font-weight: 400;
line-height: 22px;
word-wrap: break-word;
}
}
}
}
}
.right-wap {
flex: 1 1 0;
align-self: stretch;
background: var(--BG-White, white);
overflow: hidden;
border-radius: 8px;
display: flex;
flex-direction: column;
.header {
align-self: stretch;
padding-left: 20px;
padding-right: 20px;
padding-top: 28px;
padding-bottom: 28px;
justify-content: flex-start;
align-items: center;
gap: 10px;
display: inline-flex;
}
.coze-content {
align-self: stretch;
justify-content: space-between;
align-items: flex-start;
display: inline-flex;
:deep(.coze-chat-sdk) {
border-radius: none !important;
box-shadow: none !important;
}
.form {
width: 400px;
height: 796px;
padding: 20px;
position: relative;
border-right: 1px var(--Border-1, #D7D7D9) solid;
flex-direction: column;
justify-content: flex-start;
align-items: center;
gap: 32px;
display: inline-flex;
}
.res {
flex: 1 1 0;
align-self: stretch;
padding-left: 32px;
padding-right: 32px;
padding-top: 20px;
padding-bottom: 20px;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 32px;
display: inline-flex
}
}
}
}

View File

@ -0,0 +1,102 @@
<template>
<div class="agent-wrap relative h-full">
<a-input
v-model="query.name"
@press-enter="getData()"
placeholder="搜索智能体"
size="large"
allow-clear
class="absolute right-0 top--10px !w-400px"
>
<template #prefix>
<icon-search @click="getData()" />
</template>
</a-input>
<div v-for="(item, index) in list" :key="index">
<p class="span-title w-fit mb-16px">{{ item.name }}</p>
<a-row class="grid-demo" :gutter="[20, 16]" v-if="item.agent_products.length > 0">
<a-col :xs="24"
:sm="12"
:md="8"
:lg="5"
:xl="6"
:xxl="4"
v-for="(product, k) in item.agent_products" :key="k">
<div class="card-container cursor-pointer !h-252px" @click="goDetail(product?.type, product?.id)">
<div class="card-image h-120px w-100% bg-cover bg-center mb-8px" v-image-main-color="product.image_url">
<img class="object-contain h-full w-100% " :src="product?.image_url"/>
</div>
<div class="card-content w-full">
<TextoverTips :context="product.name" class="card-title mb-4px !text-16px"/>
<TextoverTips :context="product.description" class="card-description mb-8px color-#737478 text-14px lh-22px font-400" :line="2" />
</div>
<div class="card-footer">
<div
:class="['status-tag', product.type === 1 ? 'blue-tag' : 'red-tag']"
:style="{ background: product.type === 1 ? 'var(--Functional-Blue-1, #F0EDFF)' : 'var(--Functional-Red-1, #FFE9E7)' }"
data-size="mini-20px"
>
<SvgIcon
size="12"
:class="product.type === 2 ? 'color-#F64B31' : 'color-#6D4CFE'"
class="mr-4px"
:name="product.type === 2 ? 'svg-workflow' : 'svg-chatbot'"
alt="状态图标"
/>
<div class="status-text">{{ product.type === 1 ? '对话式' : '工作流' }}</div>
</div>
<div class="usage-info">
<div class="usage-count mr-2px">{{ formatNumberShow({ value: product?.views, showExactValue: true }) }}</div>
<div class="usage-label">次使用</div>
</div>
</div>
</div>
</a-col>
</a-row>
<NoData v-else />
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { getAgentList } from '@/api/all/agent';
import { formatNumberShow } from "@/utils/tools";
import TextoverTips from "@/components/text-over-tips";
const router = useRouter();
const list = ref([]);
const getData = async () => {
const { code, data } = await getAgentList(query);
list.value = data;
};
const query = reactive({
name: '',
});
const goDetail = (type: number, id: number) => {
if (type === 1) {
router.push({
path: '/agent/chat',
query: { id: id },
});
} else if (type === 2) {
router.push({
path: '/agent/workFlow',
query: { id: id },
});
}
};
onMounted(() => {
getData();
});
</script>
<style scoped lang="scss">
@import './style.scss';
</style>

View File

@ -0,0 +1,138 @@
.agent-wrap {
border-radius: 4px;
.ant-card-cover img {
height: 150px;
object-fit: cover;
}
.grid-demo {
&:not(:last-child) {
margin-bottom: 16px;
}
}
.span-title {
color: var(--Text-2, #3C4043);
font-size: 16px;
font-family: $font-family-medium;
font-weight: 400;
line-height: 24px;
word-wrap: break-word;
}
.card-container {
width: 100%;
height: 100%;
padding: 12px;
background: var(--BG-White, white);
overflow: hidden;
border-radius: 8px;
outline: 1px solid var(--BG-300, #e6e6e8);
outline-offset: -1px;
display: inline-flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
}
.card-image {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.card-content {
flex: 1;
// overflow: hidden;
.card-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
:deep(.card-title) {
color: var(--Text-1, #211f24);
font-size: 16px;
font-family: $font-family-medium;
font-weight: 400;
line-height: 24px;
word-wrap: break-word;
}
:deep(.card-description) {
color: var(--Text-3, #737478);
font-size: 14px;
font-family: $font-family-regular;
font-weight: 400;
line-height: 22px;
word-wrap: break-word;
}
:deep(.arco-input-wrapper) {
&.arco-textarea-wrapper {
height: 60px;
}
.arco-input-prefix {
padding-right: 4px;
}
}
.card-footer {
width: 100%;
// height: 100%;
display: inline-flex;
justify-content: space-between;
align-items: center;
.status-tag {
height: 20px;
padding: 0 8px;
border-radius: 2px;
display: flex;
justify-content: flex-start;
align-items: center;
// gap: 4px;
.status-text {
font-size: 12px;
font-family: $font-family-medium;
font-weight: 400;
line-height: 20px;
}
}
.blue-tag {
.status-text {
color: var(--Functional-Blue-6, #6D4CFE);
}
}
.red-tag {
.status-text {
color: var(--Functional-Red-6, #F64B31);
}
}
.usage-info {
display: flex;
justify-content: flex-start;
align-items: center;
.usage-count,
.usage-label {
color: var(--Text-3, #737478);
font-size: 12px;
font-family: $font-family-regular;
font-weight: 400;
line-height: 20px;
}
.usage-count {
font-family: $font-family-manrope-regular;
}
}
}
}

View File

@ -0,0 +1,107 @@
<template>
<div class="form-container">
<a-form :model="formData" ref="formRef" layout="vertical">
<a-form-item
v-for="(field, index) in formFields"
:key="index"
:label="field.props.label"
:field="field.props.name"
:rules="field.props.rules"
:tooltip="field.props.tip"
>
<a-input
allowClear
v-if="field.type === 'input'"
v-model="formData[field.props.name]"
:placeholder="field?.props?.placeholder"
/>
<a-textarea
v-if="field.type === 'textarea'"
style="width: 500px; height: 200px"
v-model="formData[field.props.name]"
:placeholder="field?.props?.placeholder"
/>
<a-color-picker v-if="field.type === 'color_picker'"
style="width: 500px; height: 200px"
v-model="formData[field.props.name]" />
<ImageUpload
v-if="field.type == 'upload_image'"
v-model="formData[field.props.name]"
:limit="field.props.limit"
></ImageUpload>
<FileUpload
v-if="field.type == 'upload_file'"
v-model="formData[field.props.name]"
:limit="field.props.limit"
></FileUpload>
<a-select
v-else-if="field.type === 'select'"
v-model="formData[field.props.name]"
:placeholder="field.placeholder"
>
<a-option v-for="(option, optIndex) in field.props.options" :key="optIndex" :value="option.value">
{{ option.label }}
</a-option>
</a-select>
</a-form-item>
</a-form>
<a-button class="submit-btn" type="primary" :disabled="loading" @click="handleSubmit">提交执行</a-button>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
import ImageUpload from '@/components/upload/ImageUpload.vue';
import FileUpload from '@/components/upload/FileUpload.vue';
const props = defineProps({
formFields: {
type: Array,
required: true,
},
formData: {
type: Object,
required: true,
},
loading: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['submit']);
const formRef = ref(null);
const handleSubmit = async () => {
const errors = await formRef.value.validate();
if (errors) return;
console.log(props.formFields, 'props.formFields');
emit('submit', props.formData);
};
</script>
<style scoped lang="scss">
.form-container {
:deep(.arco-input-wrapper),
:deep(.arco-textarea-wrapper) {
border-radius: 4px;
border-color: #d7d7d9;
background-color: #fff;
height: 35px;
width: 300px;
&:focus-within,
&.arco-input-focus {
background-color: var(--color-bg-2);
border-color: rgb(var(--primary-6));
box-shadow: 0 0 0 0 var(--color-primary-light-2);
}
&.arco-textarea-wrapper {
height: 60px;
}
}
.submit-btn {
display: block;
margin: 0 auto;
}
}
</style>

View File

@ -0,0 +1,20 @@
<template>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue';
import workflow from '@/assets/svg/svg-workflow.svg';
const props = defineProps({
cozeInfo: {
type: Object as () => any,
default: () => ({}),
},
});
onMounted(() => {});
</script>
<style scoped>
@import './history.scss';
</style>

View File

@ -0,0 +1,211 @@
.agent-card {
width: 100%;
height: 70vh;
background: var(--BG-100, #F7F8FA);
overflow: hidden;
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
.header-section {
align-self: stretch;
height: 300px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
.image-container {
width: 90%;
height: 100%;
border-radius: 8px;
}
.image-container img {
width: 100%;
height: 100%;
border-radius: 8px;
aspect-ratio: 1 / 1;
object-fit: cover;
}
}
.info-section {
align-self: stretch;
display: flex;
justify-content: space-between;
align-items: flex-start;
.title-group {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 4px;
padding: 10px 24px 0 24px;
.title {
color: var(--Text-1, #211F24);
font-size: 18px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 26px;
padding: 10px;
}
.tag {
height: 20px;
padding: 0 8px;
background: var(--Functional-Red-1, #FFE9E7);
border-radius: 2px;
display: flex;
justify-content: flex-start;
align-items: center;
gap: 4px;
.icon {
width: 12px;
height: 12px;
position: relative;
&::before {
content: '';
width: 10.74px;
height: 10.50px;
left: 0.63px;
top: 0.75px;
position: absolute;
background: var(--Brand-6, #6D4CFE);
}
}
.text {
color: var(--Functional-Red-6, #F64B31);
font-size: 12px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 20px;
}
}
}
.usage-info {
padding: 12px;
margin-right: 12px;
margin-top: 10px;
.count {
color: var(--Text-2, #3C4043);
font-size: 16px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 24px;
}
.label {
color: var(--Text-3, #737478);
font-size: 12px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 20px;
}
}
}
.description-section {
align-self: stretch;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
gap: 10px;
margin-left: 24px;
.description {
align-self: stretch;
color: var(--Text-2, #3C4043);
font-size: 14px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 22px;
padding: 10px;
}
}
.history-section {
align-self: stretch;
flex: 1 1 0;
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
.history-title {
align-self: stretch;
height: 40px;
display: flex;
justify-content: flex-start;
align-items: center;
gap: 10px;
.text {
color: var(--Functional-Red-6, #F64B31);
font-size: 14px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 22px;
}
}
.history-list {
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
.history-item {
align-self: stretch;
height: 40px;
padding: 8px;
border-radius: 8px;
display: flex;
justify-content: flex-start;
align-items: center;
gap: 10px;
.item-text {
flex: 1 1 0;
color: var(--Text-1, #211F24);
font-size: 14px;
font-family: 'Alibaba PuHuiTi', sans-serif;
font-weight: 400;
line-height: 22px;
}
}
}
.scroll-indicator {
width: 8px;
height: 240px;
padding: 8px 1px;
position: absolute;
left: 362px;
top: 40px;
display: flex;
justify-content: flex-start;
align-items: flex-start;
gap: 10px;
.indicator-bar {
flex: 1 1 0;
height: 80px;
background: var(--BG-600, #939499);
border-radius: 4px;
}
}
}
}

View File

@ -0,0 +1,310 @@
<template>
<div class="h-full overflow-hidden flex flex-col">
<div class="back-wap cursor-pointer mb-20px" @click="goChatIndex">
<icon-left size="16" class="color-#737478 mr-4px" />
<span class="cs">返回空间</span>
</div>
<div class="workflow-container flex-1">
<div class="left-wap mr-24px" v-if="isCollapsed == false">
<div class="w-full w-100% mb-15px h-160px rounded-8px" v-image-main-color="cozeInfo.image_url">
<img v-if="cozeInfo?.image_url" :src="cozeInfo?.image_url" class="w-full h-full object-contain" />
</div>
<div class="content mb-15px">
<div class="title-body">
<div class="text mr-4px">{{ cozeInfo.name }}</div>
<div data-尺寸="迷你-20px" data-颜色="red" class="tag-body">
<div class="">
<SvgIcon size="12" name="svg-workflow" class="color-#F64B31" />
</div>
<div class="text">工作流</div>
</div>
</div>
<div class="use-body flex items-center">
<div class="num mr-2px">{{ formatNumberShow({ value: cozeInfo?.views, showExactValue: true }) }}</div>
<div class="text">次使用</div>
</div>
</div>
<div class="description">
<div class="text">
{{ cozeInfo.description }}
</div>
</div>
<div class="out-line">
<div class="out-line-div"></div>
</div>
<div class="history">
<div class="section">
<div class="text">历史对话</div>
</div>
<div class="history-item" v-for="(item, index) in history" :key="index">
<div class="item-body">
<div class="text ellipsis-title" @click="getHistoryInfo(item)">
{{ item.title }}
</div>
<div class="trigger-container">
<a-trigger
mouse-leave-delay="200"
position="top"
trigger="hover"
:auto-fit-position="false"
:unmount-on-close="true"
>
<SvgIcon size="12" name="svg-more" class="icon-more" />
<template #content>
<div class="">
<div class="history-item-dropdown">
<div class="dropdown-item">
<SvgIcon size="12" name="svg-pin" class="icon color-#6D4CFE" />
<div @click="(event) => handleTop(item.id, item.sort, event)" class="text">
{{ item.sort > 0 ? '取消置顶' : '置顶' }}
</div>
</div>
<div class="dropdown-item">
<SvgIcon size="12" name="svg-delete" class="icon color-#6D4CFE" />
<a-popconfirm
content="你确认删除该历史对话吗"
@ok="deleteHistory(item.id, index)"
type="error"
>
<div class="text delete">删除</div>
</a-popconfirm>
</div>
</div>
</div>
</template>
</a-trigger>
</div>
</div>
</div>
</div>
</div>
<div class="right-wap">
<div class="header">
<div class="body">
<div class="">
<div class="toggle-btn cursor-pointer" @click="toggleCollapse">
<a-tooltip :content="isCollapsed ? '展开' : '折叠'">
<img class="status-icon" :src="isCollapsed ? menuUnfold : menuFold" />
</a-tooltip>
</div>
</div>
</div>
</div>
<div class="content flex-1">
<div class="form !h-full">
<DynamicForm :formFields="formFields.form" :formData="formData" :loading="loading" @submit="handleSubmit" />
</div>
<div class="res h-full">
<a-spin v-if="loading" class="spin-center" tip="生成中。。。" />
<div
class="markdown-container"
v-if="workFlowRes.output != '' && loading === false"
v-html="renderedMarkdown"
></div>
<NoData v-if="workFlowRes.output == '' && loading === false" />
</div>
</div>
</div>
</div>
<a-modal style="width: 500px" v-model:visible="editHistoryVisible">
<template #title> Title</template>
<div></div>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import DynamicForm from './components/DynamicForm.vue';
import {
executeWorkFlow,
getWorkFlowInfo,
delWorkflowHistoryApi,
getSyncWorkflowTaskApi,
topWorkflowHistoryApi,
getWorkflowHistoryListApi,
cancelTopWorkflowHistoryApi,
} from '@/api/all/agent';
import { useRoute, useRouter } from 'vue-router';
import { marked } from 'marked';
import DOMPurify from 'dompurify';
import menuFold from '@/assets/svg/menu-fold.svg';
import menuUnfold from '@/assets/svg/menu-unfold.svg';
import { formatNumberShow } from '@/utils/tools';
// import { WORKEXECUTE_STATUS } from '../AgentConstants.ts';
const editHistoryVisible = ref(false);
const formFields = ref({});
const history = ref([]);
// 是否折叠状态
const isCollapsed = ref(false);
// 切换折叠状态
const toggleCollapse = () => {
isCollapsed.value = !isCollapsed.value;
};
// 表单数据对象(动态生成初始值)
const formData = ref({});
const route = useRoute();
const id = route.query.id;
const query = reactive({
id: id,
});
const router = useRouter();
const goChatIndex = async () => {
router.push({
path: '/agent/index',
});
};
const loading = ref(false);
const cozeInfo = reactive({
name: '',
description: '',
icon_url: '',
workflow_id: '',
id: 0,
});
const getData = async () => {
const { code, data } = await getWorkFlowInfo(query.id);
console.log(data.info,'data.info')
Object.assign(cozeInfo, data.info);
formFields.value = data.form_config;
history.value = data.history;
};
const workFlowRes = reactive({
output: '',
execute_id: '',
});
// 渲染 Markdown 的计算属性
const renderedMarkdown = computed(() => {
if (workFlowRes?.output) {
const rawHtml = marked.parse(workFlowRes.output || '');
return DOMPurify.sanitize(rawHtml);
}
return '';
});
const deleteHistory = async (id, index) => {
const { code } = await delWorkflowHistoryApi(id);
if (code === 200) {
history.value.splice(index, 1);
}
};
const historyForm = reactive({
id: 0,
title: '',
});
const handleTop = async (id, sort, event) => {
if (sort > 0) {
canceltopHistory(id);
} else {
topHistory(id);
}
event.stopPropagation();
};
//置顶
const topHistory = async (id, sort) => {
const { code, message } = await topWorkflowHistoryApi(id);
if (code === 200) {
AMessage.success(message);
getWorkflowHistoryList();
}
};
//取消置顶
const canceltopHistory = async (id, sort) => {
const { code, message } = await cancelTopWorkflowHistoryApi(id);
if (code === 200) {
AMessage.success(message);
getWorkflowHistoryList();
}
};
const getWorkflowHistoryList = async () => {
const { code, data } = await getWorkflowHistoryListApi({
workflow_id: cozeInfo.workflow_id,
});
if (code === 200) {
history.value = data.list;
}
};
// 提交表单
const handleSubmit = async (formData) => {
try {
const param = { form_data: formData, id: cozeInfo.id };
console.log(param,'param')
workFlowRes.output = '';
loading.value = true;
const { code, data } = await executeWorkFlow(param);
if (code === 200) {
workFlowRes.execute_id = data.execute_id;
startTask();
}
} catch (error) {
loading.value = false;
}
};
const timerRef = ref(null);
const startTask = () => {
if (timerRef.value !== null) return;
timerRef.value = setInterval(async () => {
getSyncWorkflowTask();
}, 3000);
};
const getSyncWorkflowTask = async () => {
try {
const { code, data } = await getSyncWorkflowTaskApi({
execute_id: workFlowRes.execute_id,
workflow_id: cozeInfo.workflow_id,
});
if (code === 200) {
if (data.execute_status === 'Success' || data.execute_status === 'Fail') {
workFlowRes.output = data.output;
clearTimeout();
if (!isEmpty(data.history)) {
addHistoryItem(data.history);
}
}
}
} catch (error) {
clearTimeout();
}
};
const clearTimeout = async () => {
clearInterval(timerRef.value);
timerRef.value = null;
loading.value = false;
};
const addHistoryItem = (item) => {
history.value.unshift(item);
};
const getHistoryInfo = (item) => {
formData.value = item.param;
workFlowRes.output = item.output;
console.log(formData.value, 'formData');
};
onMounted(() => {
getData();
});
</script>
<style scoped lang="scss">
@import './style.scss';
</style>

View File

@ -0,0 +1,379 @@
.cs {
color: #3C4043;
font-family: $font-family-regular;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
}
.back-wap {
width: 100%;
justify-content: flex-start;
align-items: center;
display: inline-flex;
}
.workflow-container {
width: 100%;
// height: 100%;
justify-content: flex-start;
align-items: center;
display: inline-flex;
.left-wap {
width: 360px;
align-self: stretch;
background: var(--BG-100, #F7F8FA);
overflow: hidden;
border-radius: 8px;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
display: inline-flex;
// .header {
// align-self: stretch;
// height: 160px;
// flex-direction: column;
// justify-content: center;
// align-items: center;
// gap: 10px;
// display: flex;
// .image-body {
// align-self: stretch;
// flex: 1 1 0;
// position: relative;
// background: #FFEDED;
// overflow: hidden;
// border-radius: 8px;
// img {
// width: 408.90px;
// height: 218px;
// left: -24.45px;
// top: -29px;
// position: absolute
// }
// }
// }
.content {
align-self: stretch;
justify-content: space-between;
align-items: flex-start;
display: inline-flex;
.title-body {
justify-content: flex-start;
align-items: center;
gap: 4px;
display: flex;
.text {
justify-content: center;
display: flex;
flex-direction: column;
color: var(--Text-1, #211F24);
font-size: 18px;
font-family: $font-family-medium;
font-weight: 400;
line-height: 26px;
word-wrap: break-word;
}
.tag-body {
height: 20px;
padding-left: 8px;
padding-right: 8px;
background: var(--Functional-Red-1, #FFE9E7);
overflow: hidden;
border-radius: 2px;
justify-content: flex-start;
align-items: center;
gap: 4px;
display: flex;
.text {
color: var(--Functional-Red-6, #F64B31);
font-size: 12px;
font-family: $font-family-medium;
font-weight: 400;
line-height: 20px;
word-wrap: break-word
}
}
}
.use-body {
.num {
color: var(--Text-2, #3C4043);
font-size: 16px;
font-family: $font-family-manrope-medium;
font-weight: 400;
line-height: 24px;
word-wrap: break-word
}
.text {
color: var(--Text-3, #737478);
font-size: 12px;
font-family: $font-family-regular;
font-weight: 400;
line-height: 20px;
word-wrap: break-word;
}
}
}
.description {
align-self: stretch;
overflow: hidden;
flex-direction: column;
justify-content: flex-start;
align-items: center;
gap: 10px;
display: flex;
.text {
align-self: stretch;
color: var(--Text-2, #3C4043);
font-size: 14px;
font-family: $font-family-regular;
font-weight: 400;
line-height: 22px;
word-wrap: break-word;
}
}
.out-line {
align-self: stretch;
height: 40px;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 10px;
display: flex;
.out-line-div {
align-self: stretch;
height: 0px;
outline: 1px var(--Border-2, #E6E6E8) solid;
outline-offset: -0.50px;
}
}
.history {
align-self: stretch;
flex: 1 1 0;
position: relative;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
display: flex;
// padding: 10px;
.section {
align-self: stretch;
height: 40px;
justify-content: flex-start;
align-items: center;
gap: 10px;
display: inline-flex;
.text {
color: var(--Text-3, #737478);
font-size: 14px;
font-family: $font-family-regular;
font-weight: 400;
line-height: 22px;
word-wrap: break-word
}
}
.history-item {
align-self: stretch;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
display: flex;
.item-body {
align-self: stretch;
height: 40px;
padding: 8px;
border-radius: 8px;
justify-content: flex-start;
align-items: center;
gap: 10px;
display: inline-flex;
.text {
flex: 1 1 0;
color: var(--Text-1, #211F24);
font-size: 14px;
font-family: $font-family-regular;
font-weight: 400;
line-height: 22px;
word-wrap: break-word;
}
.ellipsis-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
}
.trigger-container {
.icon-more {
visibility: hidden;
float: right;
cursor: pointer;
}
}
&:hover {
background: var(--BG-200, #E6E6E8);
.trigger-container .icon-more {
visibility: visible;
}
}
}
}
}
}
.right-wap {
flex: 1 1 0;
align-self: stretch;
background: var(--BG-White, white);
overflow: hidden;
border-radius: 8px;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
display: inline-flex;
.header {
align-self: stretch;
padding-left: 20px;
padding-right: 20px;
padding-top: 28px;
padding-bottom: 28px;
justify-content: flex-start;
align-items: center;
gap: 10px;
display: inline-flex;
.body {
width: 20px;
height: 20px;
position: relative;
overflow: hidden;
.fold {
width: 15px;
height: 12.50px;
left: 17.50px;
top: 16.25px;
position: absolute;
transform: rotate(-180deg);
transform-origin: top left;
background: var(--Text-3, #737478)
}
}
}
.content {
align-self: stretch;
border-top: 1px var(--Border-1, #D7D7D9) solid;
justify-content: space-between;
align-items: flex-start;
display: inline-flex;
.form {
width: 400px;
height: 796px;
padding: 20px;
position: relative;
border-right: 1px var(--Border-1, #D7D7D9) solid;
flex-direction: column;
justify-content: flex-start;
align-items: center;
gap: 32px;
display: inline-flex;
}
.res {
flex: 1 1 0;
padding: 32px;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 32px;
display: inline-flex;
:deep(.markdown-container img) {
max-width: 100% !important;
max-height: auto !important;
}
}
}
}
}
.history-item-dropdown {
width: 125px;
padding-top: 8px;
padding-bottom: 4px;
left: 0px;
top: 0px;
position: absolute;
background: var(--BG-White, white);
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
border-radius: 4px;
outline: 1px solid var(--Border-1, #D7D7D9);
display: inline-flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
gap: 2px;
.dropdown-item {
align-self: stretch;
height: 36px;
padding-left: 12px;
padding-right: 12px;
display: inline-flex;
justify-content: flex-start;
align-items: center;
gap: 8px;
border-radius: 0;
background-color: transparent;
&:hover {
background: var(--BG-200, #F2F3F5);
}
.icon {
float: right;
}
.text {
flex: 1 1 0;
height: 22px;
color: var(--Text-1, #211F24);
font-size: 14px;
font-family: Alibaba PuHuiTi;
font-weight: 400;
line-height: 22px;
word-wrap: break-word;
&.delete {
color: var(--Functional-Red-6, #F64B31);
}
}
}
}