wujingjing
2025-04-02 5fb58c10b2bb44b3f2d3bdab4d7a6619271e2bbf
src/components/chat/user/index.vue
@@ -1,55 +1,131 @@
<template>
   <div class="flex px-4 py-6 rounded-lg relative flex-row-reverse" :key="`${msg.historyId}_${msg.role}`">
      <img class="rounded-full size-12 flex-0 ml-4" :src="roleImageMap[msg.role]" alt="" srcset="" />
      <div class="flex-auto flex justify-end">
         <div class="inline-flex flex-col">
            <div class="rounded-[6px] p-4 leading-relaxed group" :style="{ backgroundColor: 'rgb(197 224 255)' }">
               <!-- #region ====================== 用户操作按钮 ======================-->
               <div v-if="msg.content?.values && !isSharePage" class="absolute flex items-center bottom-0 group invisible">
                  <div class="bg-[#fff] flex items-center relative mr-4 space-x-2 flex-nowrap rounded-[6px] py-2 px-2 group-hover:visible">
                     <el-tooltip effect="dark" content="复制" placement="top">
                        <div class="flex items-center justify-center size-[20px]">
                           <i
                              class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] font-medium !text-[15px] hover:!text-[18px]"
                              @click="copyUserClick(msg)"
   <div class="flex flex-col py-6 gap-2">
      <!-- #region ====================== 附件展示 ======================-->
      <div class="flex px-4 rounded-lg relative flex-row-reverse items-center" :key="`${msg.historyId}_${msg.role}`">
         <img class="rounded-full size-12 flex-0 ml-4 invisible" :src="roleImageMap[msg.role]" alt="" srcset="" />
         <div class="flex-auto flex justify-end">
            <div class="inline-flex flex-col">
               <div class="rounded-[6px] leading-relaxed group">
                  <div v-if="msg.attachList?.length > 0" class="flex gap-3.5 w-full overflow-x-auto">
                     <div
                        v-for="(item, index) in msg.attachList"
                        :key="index"
                        class="flex items-center gap-2 bg-[#e9e9e9] px-2 py-3 rounded-lg w-[220px] relative group cursor-pointer"
                        @click="openAttachPreview(item)"
                     >
                        <template v-if="item.type === 'file'">
                           <el-image
                              :zoom-rate="1.2"
                              fit="cover"
                              class="w-[24px] rounded cursor-pointer"
                              v-if="item.model.groupType === 'image'"
                              :src="item.model.previewUrl"
                              :preview-src-list="[item.model.previewUrl]"
                           />
                        </div>
                     </el-tooltip>
                     <el-tooltip effect="dark" content="设为常用语" placement="top">
                        <div class="flex items-center justify-center size-[20px]">
                           <i
                              class="p-2 ywifont ywicon-cubelifangti cursor-pointer hover:text-[#0284ff] text-[#000] font-[590] !text-[15px] hover:!text-[18px]"
                              @click="setCommonQuestionClick(msg)"
                           />
                        </div>
                     </el-tooltip>
                     <el-tooltip effect="dark" content="分享" placement="top">
                        <div class="flex items-center justify-center size-[15px]">
                           <i
                              :class="{ 'text-[#0284ff]': msg.state === AnswerState.Unlike }"
                              class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]"
                              @click="shareClick(msg)"
                           />
                        </div>
                     </el-tooltip>
                           <div v-else class="ywifont !text-[24px] flex-0" :class="[`ywicon-${item.icon}`, item.iconClass]"></div>
                           <div class="flex flex-col gap-0.5 w-full flex-auto justify-between">
                              <div class="font-bold over-ellipsis w-full">{{ item.title }}</div>
                              <div v-if="item.model.type" class="text-info text-sm over-ellipsis w-full">
                                 {{ `${item.model.type ?? ''},${item.model.size ?? ''}` }}
                              </div>
                              <el-tooltip effect="dark" content="下载" placement="top">
                                 <span
                                    class="group-hover:visible invisible ywifont ywicon-download2 absolute right-2 cursor-pointer"
                                    @click.stop="downloadAttach(item)"
                                 ></span>
                              </el-tooltip>
                           </div>
                        </template>
                        <template v-if="item.type === 'metric'">
                           <div class="ywifont !text-[24px] flex-0" :class="[`ywicon-${item.icon}`, item.iconClass]"></div>
                           <div class="flex flex-col gap-0.5 w-full flex-auto">
                              <div class="font-bold over-ellipsis w-full">{{ item.title }}</div>
                              <div class="text-info text-sm over-ellipsis w-full">{{ `指标,${item.model.values?.length} 条记录` }}</div>
                           </div>
                           <el-tooltip effect="dark" content="引用" placement="top">
                              <span
                                 class="group-hover:visible invisible ywifont ywicon-quote absolute right-2 cursor-pointer"
                                 @click.stop="quoteAttach(item)"
                              ></span>
                           </el-tooltip>
                        </template>
                        <template v-if="item.type === 'table'">
                           <div class="ywifont !text-[24px] flex-0" :class="[`ywicon-${item.icon}`, item.iconClass]"></div>
                           <div class="flex flex-col gap-0.5 w-full flex-auto">
                              <div class="font-bold over-ellipsis w-full">{{ item.title }}</div>
                              <div class="text-info text-sm over-ellipsis w-full">{{ `业务表格,${item.model.values?.length} 条记录` }}</div>
                           </div>
                           <el-tooltip effect="dark" content="引用" placement="top">
                              <span
                                 class="group-hover:visible invisible ywifont ywicon-quote absolute right-2 cursor-pointer"
                                 @click.stop="quoteAttach(item)"
                              ></span>
                           </el-tooltip>
                        </template>
                     </div>
                  </div>
               </div>
            </div>
         </div>
      </div>
      <BusinessTablePreview :data="attachPreviewData" v-model="attachPreviewIsShow" />
               <!-- #endregion -->
               <!-- #region ====================== 消息内容 ======================-->
               <template v-if="msg.content?.values">
                  <!-- #region ====================== 回答组件 ======================-->
                  <component
                     :conclusion="msg.conclusion"
                     :is="answerTypeMapCom[msg.content.type]"
                     :data="msg.content.values"
                     :originData="msg"
                  />
      <MetricValuesPreview v-model="metricPreviewIsShow" :data="metricPreviewData" />
      <!-- #endregion -->
      <div class="flex px-4 rounded-lg relative flex-row-reverse items-center" :key="`${msg.historyId}_${msg.role}`">
         <img class="rounded-full size-12 flex-0 ml-4" :src="roleImageMap[msg.role]" alt="" srcset="" />
         <div class="flex-auto flex justify-end">
            <div class="inline-flex flex-col">
               <div class="rounded-[6px] p-4 leading-relaxed group" :style="{ backgroundColor: 'rgb(197 224 255)' }">
                  <!-- #region ====================== 用户操作按钮 ======================-->
                  <div v-if="msg.content?.values && !isSharePage" class="absolute flex items-center bottom-0 group invisible">
                     <div class="bg-[#fff] flex items-center relative mr-4 space-x-2 flex-nowrap rounded-[6px] py-2 px-2 group-hover:visible">
                        <el-tooltip effect="dark" content="复制" placement="top">
                           <div class="flex items-center justify-center size-[20px]">
                              <i
                                 class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] font-medium !text-[15px] hover:!text-[18px]"
                                 @click="copyUserClick(msg)"
                              />
                           </div>
                        </el-tooltip>
                        <el-tooltip effect="dark" content="设为常用语" placement="top">
                           <div class="flex items-center justify-center size-[20px]">
                              <i
                                 class="p-2 ywifont ywicon-cubelifangti cursor-pointer hover:text-[#0284ff] text-[#000] font-[590] !text-[15px] hover:!text-[18px]"
                                 @click="setCommonQuestionClick(msg)"
                              />
                           </div>
                        </el-tooltip>
                        <el-tooltip effect="dark" content="分享" placement="top">
                           <div class="flex items-center justify-center size-[15px]">
                              <i
                                 :class="{ 'text-[#0284ff]': msg.state === AnswerState.Unlike }"
                                 class="p-2 ywifont ywicon-fenxiang cursor-pointer hover:text-[#0284ff] !text-[15px] hover:!text-[18px]"
                                 @click="shareClick(msg)"
                              />
                           </div>
                        </el-tooltip>
                     </div>
                  </div>
                  <!-- #endregion -->
               </template>
                  <!-- #region ====================== 消息内容 ======================-->
                  <template v-if="msg.content?.values">
                     <!-- #region ====================== 回答组件 ======================-->
                     <component
                        :conclusion="msg.conclusion"
                        :is="answerTypeMapCom[msg.content.type]"
                        :data="msg.content.values"
                        :originData="msg"
                     />
               <!-- #endregion -->
                     <!-- #endregion -->
                  </template>
                  <!-- #endregion -->
               </div>
            </div>
         </div>
      </div>
@@ -57,10 +133,17 @@
</template>
<script setup lang="ts" name="UserMsg">
import { useClipboard } from '@vueuse/core';
import { ElMessage } from 'element-plus';
import { AnswerState, answerTypeMapCom, roleImageMap, type ChatMessage } from '../model/types';
import { isSharePage } from '/@/stores/chatRoom';
import { onClickOutside, useClipboard } from '@vueuse/core';
import { onActivated, ref } from 'vue';
import BusinessTablePreview from '../components/playBar/businessTablePreview/index.vue';
import { Attach } from '../components/playBar/hook/useAttach';
import MetricValuesPreview from '../components/playBar/metricValues/MetricValuesPreview.vue';
import emitter from '/@/utils/mitt';
import { downloadFileByPost } from '/@/api/file';
const emit = defineEmits<{
   (event: 'copyMsg', msgObj: ChatMessage): void;
@@ -89,5 +172,61 @@
const shareClick = (msg) => {
   emit('shareClick', msg);
};
//#region ====================== 指标附件预览 ======================
const metricPreviewIsShow = ref(false);
const metricPreviewData = ref<Attach>();
//#endregion
//#region ====================== 附件预览 ======================
const attachPreviewIsShow = ref(false);
const attachPreviewData = ref<Attach>();
const openAttachPreview = (item: Attach) => {
   if (item.type === 'file') {
      openFileContent(item);
   } else if (item.type === 'table') {
      attachPreviewIsShow.value = true;
      attachPreviewData.value = item;
   } else if (item.type === 'metric') {
      metricPreviewIsShow.value = true;
      metricPreviewData.value = item;
   }
};
//#endregion
//#region ====================== 附件引用 ======================
const quoteAttach = (item: Attach) => {
   emitter.emit('quoteAttach', item);
};
//#endregion
//#region ====================== 查看文件文本内容 ======================
const openFileContent = (item: Attach) => {
   emitter.emit('setFileContent', {
      title: item.title,
      content: item.model?.file_content ?? '',
   });
};
//#endregion
//#region ====================== 附件下载 ======================
const downloadAttach = async (item: Attach) => {
   const fileId = item.model?.file_id;
   if (!fileId) return;
   const res = await downloadFileByPost({
      file_id: fileId,
   });
   const url = window.URL.createObjectURL(res as any);
   const link = document.createElement('a');
   link.href = url;
   link.download = item.title || 'download'; // Use item title or fallback name
   document.body.appendChild(link);
   link.click();
   document.body.removeChild(link);
   window.URL.revokeObjectURL(url);
};
//#endregion
</script>
<style scoped lang="scss"></style>