| | |
| | | import { fetchEventSource } from '@microsoft/fetch-event-source' |
| | | import { getAccessToken } from '@/utils/auth' |
| | | import { config } from '@/config/axios/config' |
| | | import {refreshToken} from "@/api/login"; |
| | | |
| | | // 聊天VO |
| | | export interface ChatMessageVO { |
| | |
| | | getChatMessageListByConversationId: async (conversationId: number | null) => { |
| | | return await request.get({ |
| | | url: `/ai/chat/message/list-by-conversation-id?conversationId=${conversationId}` |
| | | }) |
| | | }, |
| | | |
| | | // 消息列表 |
| | | getChatMessagePageListByConversationId: async (params: number | null) => { |
| | | return await request.get({ |
| | | url: `/ai/chat/message/page-list-by-conversation-id`, |
| | | params: params |
| | | }) |
| | | }, |
| | | |
| | |
| | | signal: ctrl.signal |
| | | }) |
| | | }, |
| | | // 发送 Stream 消息 【工业大模型专用】 |
| | | // sendEnergyChatMessageStream: async ( |
| | | // conversationId: number, |
| | | // content: string, |
| | | // ctrl, |
| | | // enableContext: boolean, |
| | | // onMessage, |
| | | // onError, |
| | | // onClose |
| | | // ) => { |
| | | // const token = getAccessToken() |
| | | // return fetchEventSource(`${config.base_url}/ai/chat/message/send-energy-stream`, { |
| | | // method: 'post', |
| | | // headers: { |
| | | // 'Content-Type': 'application/json', |
| | | // Authorization: `Bearer ${token}` |
| | | // }, |
| | | // openWhenHidden: true, |
| | | // body: JSON.stringify({ |
| | | // conversationId, |
| | | // content, |
| | | // useContext: enableContext |
| | | // }), |
| | | // onmessage: onMessage, |
| | | // onerror: onError, |
| | | // onclose: onClose, |
| | | // signal: ctrl.signal |
| | | // }) |
| | | // }, |
| | | |
| | | // 删除消息 |
| | | deleteChatMessage: async (id: string) => { |
| | |
| | | deleteByConversationId: async (conversationId: number) => { |
| | | return await request.delete({ |
| | | url: `/ai/chat/message/delete-by-conversation-id?conversationId=${conversationId}` |
| | | }) |
| | | }, |
| | | |
| | | // // 删除消息【工业大模型专用】 |
| | | // deleteEnergyChatMessage: async (id: string) => { |
| | | // return await request.delete({ url: `/ai/chat/message/delete-energy?id=${id}` }) |
| | | // }, |
| | | |
| | | // 删除指定对话的消息【工业大模型专用】 |
| | | deleteEnergyByConversationId: async (conversationId: number) => { |
| | | return await request.delete({ |
| | | url: `/ai/chat/message/delete-energy-by-conversation-id?conversationId=${conversationId}` |
| | | }) |
| | | }, |
| | | |
| | |
| | | class="absolute right-15px top-[50%] h-40px flex translate-y-[-50%] items-center justify-between" |
| | | > |
| | | <Icon |
| | | v-if="fullscreen" |
| | | class="is-hover mr-10px cursor-pointer" |
| | | :icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'" |
| | | color="#73C4FF" |
| | | hover-color="var(--el-color-primary)" |
| | | @click="toggleFull" |
| | | /> |
| | | <Icon |
| | | class="is-hover cursor-pointer" |
| | | icon="ep:close" |
| | | hover-color="var(--el-color-primary)" |
| | |
| | | <style lang="scss"> |
| | | .history-dialog { |
| | | height: 90vh; |
| | | margin-top: 30px; |
| | | color: #73C4FF; |
| | | overflow: hidden; /* 防止内容溢出 */ |
| | | margin-top: 30px; |
| | | background: rgba(3,29,76,0.79); |
| | | border-radius: 4px 4px 4px 4px; |
| | | border: 1px solid; |
| | |
| | | |
| | | .#{$elNamespace}-dialog { |
| | | margin: 0 !important; |
| | | |
| | | &__header { |
| | | height: 40px; |
| | | padding: 0; |
| | |
| | | ref="sidebarRef"> |
| | | <ConversationList |
| | | :active-id="activeConversationId" |
| | | :quick-access="quickAccessFlag" |
| | | :model-name="modelName" |
| | | :default-message="defaultMessage" |
| | | ref="conversationListRef" |
| | | @on-conversation-create="handleConversationCreateSuccess" |
| | | @on-conversation-click="handleConversationClick" |
| | |
| | | <!-- 情况一:消息加载中 --> |
| | | <MessageLoading v-if="activeMessageListLoading" /> |
| | | <!-- 情况二:无聊天对话时 --> |
| | | <MessageNewConversation |
| | | <MessageListEmpty |
| | | v-if="!activeConversation" |
| | | @on-new-conversation="handleConversationCreate" |
| | | /> |
| | | <!-- 情况三:消息列表为空 --> |
| | | <MessageListEmpty |
| | |
| | | @input="handlePromptInput" |
| | | @compositionstart="onCompositionstart" |
| | | @compositionend="onCompositionend" |
| | | placeholder="问我任何问题...(Shift+Enter 换行,按下 Enter 发送)" |
| | | placeholder="请问我问题...(Shift+Enter 换行,按下 Enter 发送)" |
| | | ></textarea> |
| | | <div class="prompt-btns"> |
| | | <div class="content"> |
| | |
| | | import MessageList from '../message/MessageList.vue' |
| | | import MessageListEmpty from '../message/MessageListEmpty.vue' |
| | | import MessageLoading from '../message/MessageLoading.vue' |
| | | import MessageNewConversation from '../message/MessageNewConversation.vue' |
| | | import { onClickOutside } from '@vueuse/core' |
| | | import * as authUtil from "@/utils/auth"; |
| | | import {refreshToken} from "@/api/login"; |
| | | import {formatToDateTime} from "@/utils/dateUtil"; |
| | | import {ElLoading} from "element-plus"; |
| | | |
| | | /** AI 聊天对话 列表 */ |
| | | defineOptions({ name: 'NormalConversation' }) |
| | | |
| | | const props = defineProps({ |
| | | data: { |
| | | type: Object, |
| | | default: () => null |
| | | } |
| | | }) |
| | | |
| | | const route = useRoute() // 路由 |
| | | const message = useMessage() // 消息弹窗 |
| | |
| | | isCollapsed.value = !isCollapsed.value |
| | | } |
| | | |
| | | const modelName = ref<string>('common') // 对话搜索 |
| | | |
| | | // 聊天对话 |
| | | const conversationListRef = ref() |
| | | const quickAccessFlag = ref(false) |
| | | const defaultMessage = ref<ChatMessageVO>() |
| | | const activeConversationId = ref<number | null>(null) // 选中的对话编号 |
| | | const activeConversation = ref<ChatConversationVO | null>(null) // 选中的 Conversation |
| | | const conversationInProgress = ref(false) // 对话是否正在进行中。目前只有【发送】消息时,会更新为 true,避免切换对话、删除对话等操作 |
| | |
| | | // 接收 Stream 消息 |
| | | const receiveMessageFullText = ref('') |
| | | const receiveMessageDisplayedText = ref('') |
| | | |
| | | |
| | | // =========== 【聊天对话】相关 =========== |
| | | |
| | |
| | | activeMessageList.value = await ChatMessageApi.getChatMessageListByConversationId( |
| | | activeConversationId.value |
| | | ) |
| | | |
| | | // 滚动到最下面 |
| | | await nextTick() |
| | | await scrollToBottom() |
| | |
| | | return [] |
| | | }) |
| | | |
| | | //处理调度推理结论 |
| | | const dealResult = (conversations: any) => { |
| | | const regex = /<think>(\n*)([\s\S]*?)(\n*)<\/think>(\n*)([\s\S]*)/; |
| | | conversations.forEach((conversation) => { |
| | | if(conversation.content.includes('<\/think>')) { |
| | | conversation.thinkingFlag = false |
| | | } else { |
| | | conversation.thinkingFlag = true |
| | | } |
| | | const match = conversation.content.match(regex); |
| | | if(match) { |
| | | conversation.thinking = match[2]; |
| | | conversation.conclusion = match[5] |
| | | // //处理调度推理结论(deepSeek) |
| | | // const dealResult = (conversations: any) => { |
| | | // const regex = /<think>(\n*)([\s\S]*?)(\n*)<\/think>(\n*)([\s\S]*)/; |
| | | // conversations.forEach((conversation) => { |
| | | // if(conversation.content.includes('<\/think>')) { |
| | | // conversation.thinkingFlag = false |
| | | // } else { |
| | | // conversation.thinkingFlag = true |
| | | // } |
| | | // const match = conversation.content.match(regex); |
| | | // if(match) { |
| | | // conversation.thinking = match[2]; |
| | | // conversation.conclusion = match[5] |
| | | // } |
| | | // }) |
| | | // } |
| | | |
| | | //处理调度推理结论(微调大模型) |
| | | const dealResult = (messages: any) => { |
| | | messages.forEach((message) => { |
| | | if(message.type === 'assistant') { |
| | | const spliceText = message.content.includes("总结:") ? "总结:" : "结论:"; |
| | | // 创建同时捕获前后内容的正则表达式 |
| | | const regex = new RegExp(`^([\\s\\S]*?)${spliceText}([\\s\\S]*)$`); |
| | | const match = message.content.match(regex); |
| | | if(match) { |
| | | message.thinking = match[1]; |
| | | message.conclusion = match[2] |
| | | } else { |
| | | message.thinking = message.content |
| | | } |
| | | } |
| | | }) |
| | | } |
| | |
| | | message.error('发送失败,原因:内容为空!') |
| | | return |
| | | } |
| | | // 发送请求时如果accessToken过期,无法中断请求,暂时增加请求前刷新token |
| | | authUtil.setToken(await refreshToken()) |
| | | if (activeConversationId.value == null) { |
| | | message.error('还没创建对话,不能发送!') |
| | | return |
| | | await conversationListRef.value.createConversation(props.data?formatToDateTime(new Date(props.data.createTime)):null) |
| | | } |
| | | // 清空输入框 |
| | | prompt.value = '' |
| | | // 发送请求时如果accessToken过期,无法中断请求,暂时增加请求前刷新token |
| | | authUtil.setToken(await refreshToken()) |
| | | // 执行发送 |
| | | await doSendMessageStream({ |
| | | conversationId: activeConversationId.value, |
| | | content: content |
| | | } as ChatMessageVO) |
| | | setTimeout(() => { |
| | | // 执行发送 |
| | | doSendMessageStream({ |
| | | conversationId: activeConversationId.value, |
| | | content: content |
| | | } as ChatMessageVO) |
| | | }, 400) |
| | | } |
| | | |
| | | /** 真正执行【发送】消息操作 */ |
| | |
| | | conversationInProgress.value = true |
| | | // 设置为空 |
| | | receiveMessageFullText.value = '' |
| | | |
| | | try { |
| | | // 1.1 先添加两个假数据,等 stream 返回再替换 |
| | | activeMessageList.value.push({ |
| | |
| | | await nextTick() |
| | | await scrollToBottom() // 底部 |
| | | // 1.3 开始滚动 |
| | | textRoll() |
| | | |
| | | await textRoll() |
| | | // 2. 发送 event stream |
| | | let isFirstChunk = true // 是否是第一个 chunk 消息段 |
| | | await ChatMessageApi.sendChatMessageStream( |
| | |
| | | stopStream() |
| | | } |
| | | ) |
| | | } catch {} |
| | | } catch { |
| | | console.log('sendStream Exception') |
| | | } |
| | | } |
| | | |
| | | /** 停止 stream 流式调用 */ |
| | |
| | | |
| | | /** 初始化 **/ |
| | | onMounted(async () => { |
| | | // 如果有 conversationId 参数,则默认选中 |
| | | if (route.query.conversationId) { |
| | | const id = route.query.conversationId as unknown as number |
| | | activeConversationId.value = id |
| | | await getConversation(id) |
| | | defaultMessage.value = props.data |
| | | if(defaultMessage.value) { |
| | | prompt.value = defaultMessage.value.content |
| | | quickAccessFlag.value = true |
| | | } else { |
| | | // 获取列表数据 |
| | | activeMessageListLoading.value = true |
| | | await getMessageList() |
| | | } |
| | | }) |
| | | |
| | | // 获取列表数据 |
| | | activeMessageListLoading.value = true |
| | | await getMessageList() |
| | | onUnmounted(() => { |
| | | stopStream() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | |
| | | .sidebar-toggle { |
| | | position: absolute; |
| | | left: 300px; // 初始展开位置 |
| | | left: 320px; // 初始展开位置 |
| | | top: 40%; |
| | | z-index: 1000; |
| | | width: 20px; |
| | |
| | | left: 0; |
| | | top: 0; |
| | | bottom: 0; |
| | | width: 300px; |
| | | width: 320px; |
| | | background: rgba(13,28,58,0.9); |
| | | box-shadow: 2px 0 8px rgba(0,0,0,0.1); |
| | | transition: transform 0.3s ease, opacity 0.2s ease; |
| | | z-index: 999; |
| | | overflow: hidden; |
| | | overflow-x: hidden; |
| | | |
| | | &.collapsed { |
| | | transform: translateX(-100%); |
| | |
| | | // 头部 |
| | | .detail-container { |
| | | width: 100%; |
| | | height: 885px; |
| | | height: 910px; |
| | | margin-left: 5px; |
| | | background-color: rgba(0, 0, 0, 0); /* 透明背景 */ |
| | | transition: margin 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| | |
| | | .footer-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 114px; |
| | | height: 205px; |
| | | margin-left: 10px; |
| | | padding: 0; |
| | | |
| | |
| | | flex-direction: column; |
| | | padding: 9px 10px; |
| | | width: 876px; |
| | | height: 114px; |
| | | height: 205px; |
| | | background: rgba(115,196,255,0.05); |
| | | border-radius: 4px 4px 4px 4px; |
| | | border: 1px solid #73C4FF; |
| | |
| | | } |
| | | |
| | | .prompt-input { |
| | | width: 876px; |
| | | height: 113.55px; |
| | | width: 860px; |
| | | height: 203px; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | background-color: rgba(219,238,255,0); |
| | |
| | | <!-- AI 对话 --> |
| | | <template> |
| | | <el-aside width="260px" class="conversation-container h-100%"> |
| | | <el-aside width="280px" class="conversation-container h-100%"> |
| | | <!-- 左顶部:对话 --> |
| | | <div class="h-100%"> |
| | | <div class="h-80%"> |
| | | <div class="conversation-title"> |
| | | <img |
| | | src="@/assets/ai/zhuanlu/conversation_big.png" |
| | |
| | | 对话列表 |
| | | </div> |
| | | <!-- <hr class="line"/>--> |
| | | <el-button class="w-1/1 btn-new-conversation" type="primary" @click="createConversation"> |
| | | <el-button class="w-1/1 btn-new-conversation" type="primary" @click="createNewConversation"> |
| | | <img |
| | | src="@/assets/ai/zhuanlu/conversation_big.png" |
| | | class="mr-8px w-[1.5em] h-[1.5em]" |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 底部占位 --> |
| | | <div class="h-160px w-100%"></div> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation' |
| | | import { Bottom, Top } from '@element-plus/icons-vue' |
| | | import roleAvatarDefaultImg from '@/assets/ai/zhuanlu/assistant.png' |
| | | import {ChatMessageVO} from "@/api/ai/chat/message"; |
| | | import {formatToDate, formatToDateTime} from "@/utils/dateUtil"; |
| | | |
| | | const message = useMessage() // 消息弹窗 |
| | | |
| | | // 定义属性 |
| | | const searchName = ref<string>('') // 对话搜索 |
| | | const modelName = ref<string>('common') // 对话搜索 |
| | | const activeConversationId = ref<number | null>(null) // 选中的对话,默认为 null |
| | | const hoverConversationId = ref<number | null>(null) // 悬浮上去的对话 |
| | | const conversationList = ref([] as ChatConversationVO[]) // 对话列表 |
| | |
| | | activeId: { |
| | | type: String || null, |
| | | required: true |
| | | } |
| | | }, |
| | | modelName: { |
| | | type: String || null, |
| | | required: true |
| | | }, |
| | | quickAccess: { |
| | | type: Boolean || null, |
| | | required: true |
| | | }, |
| | | defaultMessage: {} |
| | | }) |
| | | |
| | | // 定义钩子 |
| | |
| | | }, 50) |
| | | |
| | | // 1.1 获取 对话数据 |
| | | conversationList.value = await ChatConversationApi.getChatConversationEnergyList(modelName.value) |
| | | if(conversationList.value.length == 0) { |
| | | await createConversation() |
| | | } |
| | | conversationList.value = await ChatConversationApi.getChatConversationEnergyList(props.modelName) |
| | | // 1.2 排序 |
| | | conversationList.value.sort((a, b) => { |
| | | return b.createTime - a.createTime |
| | |
| | | return |
| | | } |
| | | |
| | | // 2. 对话根据时间分组(置顶、今天、一天前、三天前、七天前、30 天前) |
| | | // 2. 对话根据时间分组(置顶、今天、昨天、三天前、七天前、30 天前) |
| | | conversationMap.value = await getConversationGroupByCreateTime(conversationList.value) |
| | | } finally { |
| | | // 清理定时器 |
| | |
| | | const groupMap = { |
| | | 置顶: [], |
| | | 今天: [], |
| | | 一天前: [], |
| | | 昨天: [], |
| | | 三天前: [], |
| | | 七天前: [], |
| | | 三十天前: [] |
| | |
| | | const now = Date.now() |
| | | // 定义时间间隔常量(单位:毫秒) |
| | | const oneDay = 24 * 60 * 60 * 1000 |
| | | const threeDays = 3 * oneDay |
| | | const sevenDays = 7 * oneDay |
| | | const thirtyDays = 30 * oneDay |
| | | //今天 |
| | | const today = formatToDate(new Date()) |
| | | const yesterday = formatToDate(new Date().setDate(new Date().getDate() - 1)) |
| | | for (const conversation of list) { |
| | | // 置顶 |
| | | if (conversation.pinned) { |
| | |
| | | } |
| | | // 计算时间差(单位:毫秒) |
| | | const diff = now - conversation.createTime |
| | | let conversationDate = formatToDate(conversation.createTime) |
| | | let titleDate = conversation.title.split(' ')[0] |
| | | // 根据时间间隔判断 |
| | | if (diff < oneDay) { |
| | | if (titleDate == today) { |
| | | groupMap['今天'].push(conversation) |
| | | } else if (diff < threeDays) { |
| | | groupMap['一天前'].push(conversation) |
| | | } else if (titleDate == yesterday) { |
| | | groupMap['昨天'].push(conversation) |
| | | } else if (diff < sevenDays) { |
| | | groupMap['三天前'].push(conversation) |
| | | } else if (diff < thirtyDays) { |
| | |
| | | } |
| | | |
| | | /** 新建对话 */ |
| | | const createConversation = async () => { |
| | | const createNewConversation = async () => { |
| | | await createConversation(null) |
| | | } |
| | | |
| | | /** 新建对话 */ |
| | | const createConversation = async (title: String) => { |
| | | // 1. 新建对话 |
| | | const conversationId = await ChatConversationApi.createChatConversationEnergy( |
| | | {modelName: modelName.value} as unknown as ChatConversationVO |
| | | {modelName: props.modelName, title: title} as unknown as ChatConversationVO |
| | | ) |
| | | // 2. 获取对话内容 |
| | | await getChatConversationList() |
| | | // 3. 选中对话 |
| | | await handleConversationClick(conversationId) |
| | | // 4. 回调 |
| | | emits('onConversationCreate') |
| | | // // 4. 回调 |
| | | // emits('onConversationCreate') |
| | | } |
| | | |
| | | /** 修改对话的标题 */ |
| | |
| | | onMounted(async () => { |
| | | // 获取 对话列表 |
| | | await getChatConversationList() |
| | | // 默认选中 |
| | | if (props.activeId) { |
| | | activeConversationId.value = props.activeId |
| | | } else { |
| | | // 首次默认选中第一个 |
| | | if(!props.quickAccess) { |
| | | // 默认选中 |
| | | if (props.activeId) { |
| | | activeConversationId.value = props.activeId |
| | | } else { |
| | | // 首次默认选中第一个 |
| | | if (conversationList.value.length) { |
| | | activeConversationId.value = conversationList.value[0].id |
| | | // 回调 onConversationClick |
| | | await emits('onConversationClick', conversationList.value[0]) |
| | | } |
| | | } |
| | | } else if(props.defaultMessage) { |
| | | let tempTitle = formatToDateTime(new Date(props.defaultMessage.createTime)) |
| | | if (conversationList.value.length) { |
| | | activeConversationId.value = conversationList.value[0].id |
| | | // 回调 onConversationClick |
| | | await emits('onConversationClick', conversationList.value[0]) |
| | | conversationList.value.forEach((item) => { |
| | | if(item.title === tempTitle) { |
| | | activeConversationId.value = item.id |
| | | // 回调 onConversationClick |
| | | emits('onConversationClick', item) |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | |
| | | } |
| | | |
| | | .conversation-list { |
| | | overflow: auto; |
| | | overflow-y: auto; |
| | | height: 100%; |
| | | margin-top: 10px; |
| | | /* Firefox */ |
| | | scrollbar-width: thin; |
| | | scrollbar-color: rgba(0, 0, 0, 0.15) transparent; |
| | | |
| | | /* WebKit */ |
| | | &::-webkit-scrollbar { |
| | | width: 6px; |
| | | background: transparent; |
| | | } |
| | | &::-webkit-scrollbar-thumb { |
| | | border-radius: 4px; |
| | | background: rgba(0, 0, 0, 0.15); |
| | | transition: background 0.3s; |
| | | &:hover { background: rgba(0, 0, 0, 0.25); } |
| | | } |
| | | .classify-title { |
| | | padding-top: 10px; |
| | | b { |
| | | color: white; |
| | | } |
| | | } |
| | | |
| | | .conversation-item { |
| | | margin-top: 5px; |
| | | } |
| | |
| | | } |
| | | |
| | | .title { |
| | | padding: 2px 10px; |
| | | padding: 2px 5px; |
| | | max-width: 220px; |
| | | font-size: 14px; |
| | | font-weight: 400; |
| | | font-size: 13px; |
| | | color: rgba(0, 0, 0, 0.77); |
| | | overflow: hidden; |
| | | white-space: nowrap; |
| | |
| | | } |
| | | } |
| | | |
| | | // 角色仓库、清空未设置对话 |
| | | // 清空未设置对话 |
| | | .tool-box { |
| | | bottom: 0; |
| | | display: flex; |
| | | padding: 0 20px; |
| | | width: 90%; |
| | | margin-left: 5%; |
| | | background-color: rgba(69,133,255,0.2); |
| | | box-shadow: 0 0 1px 1px rgba(69,133,255,0.4); |
| | | line-height: 35px; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | color: var(--el-text-color); |
| | | |
| | | border-radius: 2px; |
| | | div { |
| | | display: flex; |
| | | margin-left: 20%; |
| | |
| | | <template> |
| | | <DialogHistory title="历史建议" v-model="dialogVisible" width="1200"> |
| | | <!-- 左侧:对话列表 --> |
| | | <ConversationList |
| | | v-show="false" |
| | | :active-id="activeConversationId" |
| | | ref="conversationListRef" |
| | | /> |
| | | <!-- 右侧:对话详情 --> |
| | | <DialogHistory title="历史建议" v-model="dialogVisible" width="1200" custom-class="transparent-dialog"> |
| | | <!-- 搜索工作栏 --> |
| | | <el-form |
| | | class="-mb-15px query-area" |
| | | :model="queryParams" |
| | | ref="queryFormRef" |
| | | :inline="true" |
| | | label-width="68px" |
| | | > |
| | | <el-form-item label="对话时间" prop="createTime"> |
| | | <el-date-picker |
| | | v-model="queryParams.createTime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | type="datetimerange" |
| | | start-placeholder="开始日期" |
| | | end-placeholder="结束日期" |
| | | :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
| | | class="!w-360px transparent-date-picker-popper" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button @click="handleQuery"> |
| | | <Icon icon="ep:search" class="mr-5px"/> |
| | | 搜索 |
| | | </el-button> |
| | | <el-button @click="resetQuery"> |
| | | <Icon icon="ep:refresh" class="mr-5px"/> |
| | | 重置 |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <!-- 对话详情 --> |
| | | <el-container class="detail-container"> |
| | | <el-header class="header"> |
| | | <div class="title"> |
| | | {{ activeConversation?.title ? activeConversation?.title : '' }} |
| | | <span v-if="activeMessageList.length">({{ activeMessageList.length }})</span> |
| | | <span v-if="total">({{ total }})</span> |
| | | </div> |
| | | <div class="btns" v-if="activeConversation"> |
| | | <el-button size="small" class="btn" @click="handlerMessageClear"> |
| | |
| | | ref="messageRef" |
| | | :conversation="activeConversation" |
| | | :list="activeMessageList" |
| | | :gotoManualMethod="gotoManual" |
| | | /> |
| | | </div> |
| | | </el-main> |
| | | </el-container> |
| | | |
| | | <!-- 分页 --> |
| | | <Pagination |
| | | :total="total" |
| | | v-model:page="queryParams.pageNo" |
| | | v-model:limit="queryParams.pageSize" |
| | | @pagination="handleQuery" |
| | | /> |
| | | </DialogHistory> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import {ChatMessageApi, ChatMessageVO} from '@/api/ai/chat/message' |
| | | import { ChatConversationVO } from '@/api/ai/chat/conversation' |
| | | import ConversationList from '../conversation/HistoryConversationList.vue' |
| | | import HistoryMessageList from './HistoryMessageList.vue' |
| | | import MessageListEmpty from './MessageListEmpty.vue' |
| | | import ConversationListEmpty from '../conversation/ConversationListEmpty.vue' |
| | | import {formatDate} from "@vueuse/core"; |
| | | import {ref} from "vue"; |
| | | |
| | | /** AI 聊天对话 列表 */ |
| | | defineOptions({ name: 'HistoryMessageDialog' }) |
| | | |
| | | const route = useRoute() // 路由 |
| | | // 接收父组件传递的方法 |
| | | const props = defineProps({ |
| | | parentMethod: Function, |
| | | gotoManualMethod: Function |
| | | }); |
| | | |
| | | // 定义发射事件 |
| | | const emit = defineEmits(['gotoManualMethod']) |
| | | |
| | | const message = useMessage() // 消息弹窗 |
| | | const total = ref(0) // 历史建议列表 |
| | | |
| | | const dialogVisible = ref(false) // 弹窗的是否展示 |
| | | |
| | | const queryFormRef = ref() // 搜索的表单 |
| | | const queryParams = reactive({ |
| | | pageNo: 1, |
| | | pageSize: 10, |
| | | createTime: [], |
| | | }) |
| | | |
| | | // 聊天对话 |
| | | const conversationListRef = ref() |
| | | const activeConversation = ref<ChatConversationVO | null>(null) // 选中的 Conversation |
| | | |
| | | // 消息列表 |
| | |
| | | |
| | | |
| | | /** 打开弹窗 */ |
| | | const open = async (messages: ChatMessageVO[], conversation: ChatConversationVO) => { |
| | | const open = async (messages: ChatMessageVO[], conversation: ChatConversationVO, activeHistoryMessageTotal: number) => { |
| | | dialogVisible.value = true |
| | | total.value = activeHistoryMessageTotal |
| | | await nextTick() // 等待弹窗DOM挂载 |
| | | activeMessageList.value = messages |
| | | activeConversation.value = conversation |
| | | } |
| | | |
| | | defineExpose({ open }) // 提供方法给 parent 调用 |
| | | /** 处理查询时间段 */ |
| | | const dealDate = async () => { |
| | | const currentDate = new Date(); |
| | | const previousDate = new Date(currentDate.getTime() - 2 * 60 * 60 * 1000); |
| | | queryParams.createTime[0] = formatDate(previousDate, 'YYYY-MM-DD HH:mm:ss'); |
| | | queryParams.createTime[1] = formatDate(currentDate, 'YYYY-MM-DD HH:mm:ss'); |
| | | return queryParams; |
| | | } |
| | | |
| | | defineExpose({ open, dealDate }) // 提供方法给 parent 调用 |
| | | |
| | | /** 回到 message 列表的顶部 */ |
| | | const handleGoTopMessage = () => { |
| | |
| | | } catch {} |
| | | } |
| | | |
| | | const gotoManual = async (item: ChatMessageVO) => { |
| | | emit('gotoManualMethod', item) // 发送数据给父组件 |
| | | } |
| | | |
| | | /** 搜索按钮操作 */ |
| | | const handleQuery = async () => { |
| | | if (props.parentMethod) { |
| | | // props.parentMethod({ data: queryParams }); // 可传递参数 |
| | | let pageResult = await props.parentMethod(queryParams) |
| | | activeMessageList.value = pageResult.list |
| | | total.value = pageResult.total |
| | | } |
| | | } |
| | | |
| | | /** 重置按钮操作 */ |
| | | const resetQuery = () => { |
| | | queryFormRef.value.resetFields() |
| | | queryParams.pageNo = 1 |
| | | handleQuery() |
| | | } |
| | | |
| | | /** 初始化 **/ |
| | | onMounted(async () => { |
| | | // await dealDate() |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | |
| | | .query-area { |
| | | margin-top: 10px; |
| | | float: right; |
| | | :deep(.el-form-item__label) { |
| | | color: #73C4FF; |
| | | } |
| | | :deep(.el-date-editor .el-icon) { |
| | | color: #DBEEFF; |
| | | } |
| | | :deep(.el-date-editor .el-range-input) { |
| | | color: rgba(219, 238, 255, 0.5); |
| | | } |
| | | /* 移除所有输入框边框 */ |
| | | :deep(.el-form-item .el-input__wrapper) { |
| | | border: none !important; |
| | | box-shadow: none !important; |
| | | background: rgba(255,255,255,0.1) !important; /* 保留浅色背景 */ |
| | | } |
| | | /* 所有状态通用透明背景 */ |
| | | :deep(.el-button) { |
| | | background: transparent !important; |
| | | border-color: currentColor; /* 保持与文字同色 */ |
| | | color: #409EFF; /* 蓝色文字 */ |
| | | } |
| | | |
| | | /* 悬停状态 */ |
| | | :deep(.el-button:hover) { |
| | | background: rgba(0, 0, 0, 0.5) !important; /* 轻微悬停反馈 */ |
| | | } |
| | | |
| | | /* 点击状态 */ |
| | | :deep(.el-button:active) { |
| | | background: rgba(0, 0, 0, 0.8) !important; |
| | | } |
| | | } |
| | | |
| | | // 头部 |
| | | .detail-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | width: 100%; |
| | | height: 820px; |
| | | height: 75vh; |
| | | background-color: rgba(0, 0, 0, 0); /* 透明背景 */ |
| | | z-index: 1; |
| | | .header { |
| | |
| | | :deep(.el-button:active) { |
| | | background: rgba(0, 0, 0, 0.1) !important; |
| | | } |
| | | |
| | | /* 禁用状态 */ |
| | | :deep(.el-button.is-disabled) { |
| | | opacity: 0.6; |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | /* 下拉组件 */ |
| | | :deep(.el-select) { |
| | | /* 下拉箭头 */ |
| | | .el-select__caret { |
| | | color: #73C4FF !important; /* 匹配图中的浅蓝箭头 */ |
| | | font-size: 16px !important; |
| | | } |
| | | } |
| | | |
| | | /* 深度选择器调整边框细节 */ |
| | | :deep(.el-select__wrapper) { |
| | | background-color: transparent !important; |
| | | border-radius: 6px; /* 圆角大小 */ |
| | | border-width: 1.5px; /* 边框粗细 */ |
| | | box-shadow: 0 0 0 1px #1E5A86 !important; /* 聚焦阴影 */ |
| | | } |
| | | } |
| | | } |
| | | |
| | | .el-pagination { |
| | | //--el-pagination-button-bg-color: transparent; |
| | | opacity: 0.6; |
| | | :deep(.el-pagination__total) { |
| | | color: white; |
| | | } |
| | | :deep(.el-pager) { |
| | | color: rgba(3,27,21); |
| | | font-weight: bold; |
| | | } |
| | | :deep(.el-pagination__jump) { |
| | | color: white; |
| | | } |
| | | :deep(.el-select__popper) { |
| | | background-color: transparent; |
| | | } |
| | | :deep(.el-scrollbar) { |
| | | --el-scrollbar-opacity: 0.8; |
| | | --el-scrollbar-bg-color: transparent; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <div> |
| | | <el-text class="time">{{ formatDate(item.createTime) }}</el-text> |
| | | </div> |
| | | <div class="right-text-container"> |
| | | <div class="right-text-container question" @click="gotoManual(item)"> |
| | | <div class="right-text">{{ item.content }}</div> |
| | | </div> |
| | | <div class="right-btns"> |
| | |
| | | import { ArrowDownBold } from '@element-plus/icons-vue' |
| | | import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message' |
| | | import { ChatConversationVO } from '@/api/ai/chat/conversation' |
| | | import { useUserStore } from '@/store/modules/user' |
| | | import userAvatarDefaultImg from '@/assets/ai/zhuanlu/user.png' |
| | | import roleAvatarDefaultImg from '@/assets/ai/zhuanlu/assistant.png' |
| | | |
| | | const dialogVisible = ref(false) // 弹窗的是否展示 |
| | | |
| | | const message = useMessage() // 消息弹窗 |
| | | const { copy } = useClipboard() // 初始化 copy 到粘贴板 |
| | | const userStore = useUserStore() |
| | | |
| | | // 判断“消息列表”滚动的位置(用于判断是否需要滚动到消息最下方) |
| | | const messageContainer: any = ref(null) |
| | |
| | | |
| | | const userAvatar = computed(() => userAvatarDefaultImg) |
| | | const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg) |
| | | |
| | | |
| | | // 定义 props |
| | | const props = defineProps({ |
| | |
| | | list: { |
| | | type: Array as PropType<ChatMessageVO[]>, |
| | | required: true |
| | | } |
| | | }, |
| | | |
| | | gotoManualMethod: Function |
| | | }) |
| | | |
| | | const { list } = toRefs(props) // 消息列表 |
| | |
| | | |
| | | // ============ 处理消息操作 ============== |
| | | |
| | | const gotoManual = async (item: ChatMessageVO) => { |
| | | if(props.gotoManualMethod) { |
| | | props.gotoManualMethod(item) |
| | | } |
| | | } |
| | | |
| | | /** 复制 */ |
| | | const copyContent = async (content) => { |
| | | await copy(content) |
| | |
| | | flex-direction: column; |
| | | text-align: left; |
| | | margin: 0 15px; |
| | | |
| | | .question:hover { |
| | | cursor: pointer; |
| | | background: rgba(40, 139, 255, 0.3); |
| | | } |
| | | |
| | | .time { |
| | | text-align: left; |
| | |
| | | bottom: 0; |
| | | right: 50%; |
| | | .el-button { |
| | | background: rgba(255,215,0,0.2); |
| | | border: solid 1px rgba(255,215,0,0.8); |
| | | color: rgba(255,215,0,0.8); |
| | | background: rgba(255,255,255,0.1); |
| | | border: solid 1px rgba(255,215,0,0.6); |
| | | color: rgba(255,215,0,0.5); |
| | | } |
| | | .el-button:hover { |
| | | cursor: pointer; |
| | | background-color: rgba(255,255,255,0.4); |
| | | border: solid 2px rgba(255,215,0); |
| | | color: rgba(255,215,0); |
| | | } |
| | | } |
| | | </style> |
| | |
| | | overflow-wrap: break-word; |
| | | background: rgba(115,196,255,0); |
| | | border-radius: 4px 4px 4px 4px; |
| | | padding: 20px 10px 5px 0; |
| | | padding: 0 10px 0 0; |
| | | .left-text { |
| | | color: rgba(219,238,255,0.8); |
| | | font-size: 1rem; |
| | |
| | | } |
| | | } |
| | | |
| | | // 回到底部 |
| | | .to-bottom { |
| | | position: absolute; |
| | | z-index: 1000; |
| | | bottom: 0; |
| | | right: 50%; |
| | | |
| | | .el-button { |
| | | background: rgba(255, 255, 255, 0.1); |
| | | border: solid 1px rgba(255, 215, 0, 0.6); |
| | | color: rgba(255, 215, 0, 0.5); |
| | | } |
| | | |
| | | .el-button:hover { |
| | | cursor: pointer; |
| | | background-color: rgba(255, 255, 255, 0.4); |
| | | border: solid 2px rgba(255, 215, 0); |
| | | color: rgba(255, 215, 0); |
| | | } |
| | | } |
| | | </style> |
| | |
| | | |
| | | <div class="gas-scheduling-center"> |
| | | <div class="mode-switch"> |
| | | <el-radio-group v-model="tabPosition" class="custom-radio-group"> |
| | | <el-radio-group v-model="tabPosition" @change="handleChange" class="custom-radio-group"> |
| | | <el-radio-button label="model">大模型模式</el-radio-button> |
| | | <el-radio-button label="conversation">对话模式</el-radio-button> |
| | | </el-radio-group> |
| | |
| | | <!-- 历史建议 --> |
| | | <HistoryMessageDialog |
| | | ref="historyMessageRef" |
| | | :conversation="activeConversation" |
| | | :parentMethod="queryHistoryMessage" |
| | | @gotoManualMethod="gotoManual" |
| | | /> |
| | | </div> |
| | | |
| | | <div v-else> |
| | | <NormalConversation /> |
| | | <NormalConversation |
| | | :data="defaultMessage" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | import {round} from "lodash-es"; |
| | | import {ArrowUpBold} from "@element-plus/icons-vue"; |
| | | import * as authUtil from "@/utils/auth"; |
| | | import HistoryMessageList from "@/views/ai/dashboard/components/message/HistoryMessageList.vue"; |
| | | |
| | | const mqhsList = ref([ |
| | | { |
| | |
| | | const messageRef = ref() |
| | | const activeMessageList = ref<ChatMessageVO[]>([]) // 选中对话的消息列表 |
| | | const activeHistoryMessageList = ref<ChatMessageVO[]>([]) // 历史建议列表 |
| | | const activeHistoryMessageTotal = ref(0) // 历史建议总数 |
| | | const activeMessageListLoading = ref<boolean>(false) // activeMessageList 是否正在加载中 |
| | | const activeMessageListLoadingTimer = ref<any>() // activeMessageListLoading Timer 定时器。如果加载速度很快,就不进入加载中 |
| | | // 消息滚动 |
| | |
| | | const historyMessageRef = ref() |
| | | const openHistoryMessage = async () => { |
| | | // 刷新 message 列表 |
| | | await getHistoryMessageList() |
| | | historyMessageRef.value.open(activeHistoryMessageList.value, activeConversation.value) |
| | | let resDate = await historyMessageRef.value.dealDate() |
| | | await getHistoryMessageList(resDate) |
| | | historyMessageRef.value.open(activeHistoryMessageList.value, activeConversation.value, activeHistoryMessageTotal.value) |
| | | } |
| | | |
| | | const queryHistoryMessage = async (queryParams: ChatMessageVO) => { |
| | | return await getHistoryMessageList(queryParams) |
| | | } |
| | | |
| | | //切换对话模式判断 |
| | | const handleChange = async () => { |
| | | // 对话进行中,不允许切换 |
| | | if (conversationInProgress.value) { |
| | | message.alert('对话中,不允许切换!') |
| | | return false |
| | | } |
| | | } |
| | | |
| | | // 默认选中消息 |
| | | const defaultMessage = ref<ChatMessageVO>() |
| | | |
| | | const gotoManual = async (item: ChatMessageVO) => { |
| | | defaultMessage.value = item |
| | | tabPosition.value = 'conversation' |
| | | } |
| | | |
| | | // =========== 【聊天对话】相关 =========== |
| | |
| | | } |
| | | |
| | | /** 获取消息 message 列表 */ |
| | | const getHistoryMessageList = async () => { |
| | | const getHistoryMessageList = async (params: any) => { |
| | | if (activeConversationId.value === null) { |
| | | return |
| | | } |
| | | params.conversationId = activeConversationId.value |
| | | // 获取消息列表 |
| | | activeHistoryMessageList.value = await ChatMessageApi.getChatMessageListByConversationId( |
| | | activeConversationId.value |
| | | ) |
| | | if (activeHistoryMessageList.value.length > 0) { |
| | | let pageResult = await ChatMessageApi.getChatMessagePageListByConversationId(params) |
| | | activeHistoryMessageList.value = pageResult.list |
| | | activeHistoryMessageTotal.value = pageResult.total |
| | | if (activeHistoryMessageList.value != null && activeHistoryMessageList.value.length > 0) { |
| | | activeHistoryMessageList.value.forEach((message: ChatMessageVO) => { |
| | | if(message.type != 'user') { |
| | | dealResult(message) |
| | | } |
| | | }) |
| | | return activeHistoryMessageList.value |
| | | } |
| | | return pageResult |
| | | } |
| | | //处理调度推理结论 |
| | | const dealResult = (message: any) => { |
| | |
| | | const messageList = computed(() => { |
| | | if (activeMessageList.value.length > 0) { |
| | | activeMessageList.value[1].thinking = dealResultAndData(activeMessageList.value[1].content) |
| | | console.log(activeMessageList.value) |
| | | return activeMessageList.value |
| | | } |
| | | // 没有消息时,如果有 systemMessage 则展示它 |
| | |
| | | let returnValue = 0; |
| | | if(type == 'max') { |
| | | returnValue = computed(() => { |
| | | return Math.max(...tank) + 20 |
| | | return Number((Math.max(...tank) + 20).toFixed(0)) |
| | | }) |
| | | } else if(type == 'min') { |
| | | returnValue = computed(() => { |
| | | return Math.min(...tank) - 60 |
| | | return Number((Math.min(...tank) - 60).toFixed(0)) |
| | | }) |
| | | } else if(type == 'average') { |
| | | returnValue = computed(() => { |
| | |
| | | tank.forEach((item) => { |
| | | sum += item[0] |
| | | }) |
| | | return (sum / tank.length).toFixed(0); |
| | | return Number((sum / tank.length).toFixed(0)); |
| | | }) |
| | | } |
| | | return returnValue.value |
| | |
| | | |
| | | // 清理监听 |
| | | onUnmounted(() => { |
| | | console.log('stopStream') |
| | | const events = ['fullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange']; |
| | | events.forEach(event => { |
| | | document.removeEventListener(event, handleFullscreenChange); |
| | |
| | | |
| | | </template> |
| | | <script lang="ts" setup> |
| | | import {DICT_TYPE, getIntDictOptions} from '@/utils/dict' |
| | | import {dateFormatter} from '@/utils/formatTime' |
| | | import download from '@/utils/download' |
| | | import { DICT_TYPE } from '@/utils/dict' |
| | | import * as AiQuestionTemplateApi from '@/api/ai/questiontemplate' |
| | | import TemplateForm from './templateForm.vue' |
| | | import * as AiModelApi from "@/api/ai/model/model"; |
| | |
| | | return data.label.includes(value) |
| | | } |
| | | |
| | | // let xAxisData = [] |
| | | let xAxisData = [] |
| | | |
| | | /** 查询列表 */ |
| | | const getList = async (isClear = true) => { |
| | |
| | | formData.value.predictTime = data.predictTime; |
| | | formData.value.startTime = data.startTime |
| | | formData.value.endTime = data.endTime |
| | | |
| | | // 默认影响时间 |
| | | changeInfluenceFactorTime(data.predictTime); |
| | | |
| | | // 获取影响因素结果列表 |
| | | influenceFactorResultList.value = await influenceFactorApi.getResultList({ |
| | | outIds: outIds, |
| | | startTime: data.startTime, |
| | | endTime: data.endTime |
| | | }) |
| | | |
| | | // 获取影响因素结果列表 |
| | | influenceFactorList.value = await influenceFactorApi.getListByOutId(formData.value.checkedItemData.id) |
| | | if (influenceFactorList.value && influenceFactorList.value.length > 0) { |
| | | // 根据factorOutputId去重,因为不同的统计规则会有重复的影响因素 |
| | | influenceFactorList.value = Array.from(new Map(influenceFactorList.value.map(item => [item.factorOutputId, item])).values()); |
| | | // 默认选中第一个影响因素 |
| | | influenceFactor.value = influenceFactorList.value?.[0]?.factorOutputId |
| | | getInfluenceFactorChart(influenceFactorList.value?.[0]?.factorOutputId) |
| | | } |
| | | |
| | | |
| | | const paramsAlarm = reactive({ |
| | |
| | | symbol: ['circle', 'none'], |
| | | }, |
| | | }); |
| | | itemDataObject.value = {} |
| | | yAxisData.push({ |
| | | type: 'value', |
| | | name: "累计值", |
| | |
| | | }) |
| | | for (let i = 0; i < data.dataViewList.length; i++) { |
| | | let dataView = data.dataViewList[i] |
| | | itemDataObject.value[dataView.outId] = dataView; |
| | | let maxValue = dataView.maxValue; |
| | | let minValue = dataView.minValue; |
| | | yAxisIndex = (formData.value.isMultipleY ? i : 0) + 1; |
| | |
| | | function changeInfluenceFactorTime(time) { |
| | | if (time && new Date(time)?.getTime()) { |
| | | influenceFactorResultTime.value = time |
| | | influenceFactorResult.value = influenceFactorResultList.value?.[formData.value.checkedItemData?.id]?.filter(e => e.time === new Date(time).getTime()).sort((a, b) => b.value - a.value) || []; |
| | | influenceFactorResult.value = influenceFactorResultList.value?.[calRateForm.value.calItem]?.filter(e => e.time === new Date(time).getTime()).sort((a, b) => b.value - a.value) || []; |
| | | } |
| | | }// 选择影响因素 |
| | | function changeInfluenceFactor(value) { |
| | |
| | | if (checked.checkedNodes) { |
| | | let cns = [...checked.checkedNodes] |
| | | for (let i = 0; i < cns.length; i++) { |
| | | if (cns[i].id.indexOf('-') !== -1) { |
| | | if (cns[i].disabled) { |
| | | continue |
| | | } |
| | | formData.value.checkedItemData.push(cns[i]) |
| | |
| | | }, wait) |
| | | } |
| | | |
| | | function calItemBaseVale() { |
| | | const calItemBaseVale = async () => { |
| | | if (!calRateForm.value.calItem) { |
| | | calRateForm.value.itemPreMax = 0; |
| | | calRateForm.value.itemPreMin = 0; |
| | |
| | | calDeviation(dataView.cumulantRealData,dataView.cumulantPreData,'deviationCumulant') |
| | | calAccuracyRate() |
| | | |
| | | // 影响因素 |
| | | // 获取影响因素结果列表 |
| | | influenceFactorList.value = influenceFactorApi.getListByOutId(formData.value.checkedItemData.id) |
| | | // 根据factorOutputId去重,因为不同的统计规则会有重复的影响因素 |
| | | influenceFactorList.value = Array.from(new Map(influenceFactorList.value.map(item => [item.factorOutputId, item])).values()); |
| | | const outPutId = calRateForm.value.calItem |
| | | influenceFactorResultList.value = await influenceFactorApi.getResultList({ |
| | | outIds: [outPutId], |
| | | startTime: formData.value.startTime, |
| | | endTime: formData.value.endTime |
| | | }) |
| | | |
| | | // 默认影响时间 |
| | | changeInfluenceFactorTime(formData.value.predictTime); |
| | | |
| | | // 获取影响因素列表 |
| | | influenceFactorList.value = await influenceFactorApi.getListByOutId(outPutId) |
| | | if (influenceFactorList.value && influenceFactorList.value.length > 0) { |
| | | // 根据factorOutputId去重,因为不同的统计规则会有重复的影响因素 |
| | | influenceFactorList.value = Array.from(new Map(influenceFactorList.value.map(item => [item.factorOutputId, item])).values()); |
| | | // 默认选中第一个影响因素 |
| | | influenceFactor.value = influenceFactorList.value?.[0]?.factorOutputId |
| | | getInfluenceFactorChart(influenceFactorList.value?.[0]?.factorOutputId) |
| | | }else { |
| | | // 清除历史 |
| | | influenceFactor.value = undefined |
| | | myInfluenceFactorChart.clear() |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | label="结果code" |
| | | header-align="center" |
| | | align="left" |
| | | min-width="150" |
| | | min-width="50" |
| | | /> |
| | | <el-table-column |
| | | prop="resultData" |
| | | label="结果数据" |
| | | header-align="center" |
| | | min-width="150" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | |
| | | label="操作" |
| | | header-align="center" |
| | | align="center" |
| | | min-width="150" |
| | | min-width="50" |
| | | /> |
| | | <el-table-column |
| | | prop="reason" |
| | | label="原因" |
| | | header-align="center" |
| | | align="center" |
| | | min-width="100" |
| | | /> |
| | | <el-table-column |
| | | prop="handler" |
| | | label="处理人" |
| | | header-align="center" |
| | | align="center" |
| | | min-width="150" |
| | | min-width="100" |
| | | /> |
| | | <el-table-column |
| | | prop="handleTime" |
| | | label="处理时间" |
| | | :formatter="dateFormatter" |
| | | header-align="center" |
| | | align="center" |
| | | min-width="150" |
| | | min-width="100" |
| | | /> |
| | | </el-table> |
| | | <!-- 分页 --> |
| | |
| | | import type {DrawerProps} from 'element-plus' |
| | | import { getSuggestOperationRecordPage } from '@/api/model/sche/suggest/suggestOperationRecord'; |
| | | import SuggestSnapshot from './suggestSnapshot.vue' |
| | | import {dateFormatter} from '@/utils/formatTime' |
| | | import {ref} from "vue"; |
| | | |
| | | defineOptions({name: 'SuggestOperationRecord'}) |
| | |
| | | </el-checkbox-group> |
| | | |
| | | <div |
| | | v-for="(chart, index) in charts" |
| | | v-for="chart in charts" |
| | | :key="chart.id" |
| | | class="chart-container" |
| | | :ref="el => chartDoms[index] = el" |
| | | :ref="el => chartDoms[chart.id] = el" |
| | | v-loading="loading" |
| | | ></div> |
| | | </el-dialog> |
| | |
| | | const visible = ref(false) |
| | | const dataList = ref([]) |
| | | const selectedData = ref([]) |
| | | const charts = ref([]) |
| | | const chartDoms = ref([]) |
| | | const charts = ref() |
| | | const chartDoms = ref({}) |
| | | const chartInstances = ref([]) |
| | | const loading = ref(false) |
| | | |
| | |
| | | |
| | | /** 渲染图表 */ |
| | | const renderCharts = () => { |
| | | chartInstances.value = chartDoms.value.map((dom, index) => { |
| | | chartInstances.value = charts.value.map((chartInfo, index) => { |
| | | const dom = chartDoms.value[chartInfo.id] |
| | | if (!dom) return null |
| | | const chart = echarts.init(dom) |
| | | const chartInfo = charts.value[index] |
| | | |
| | | if (!chartInfo) return chart |
| | | |