1、这篇文章主要是写给自己以后看的
2、对接会话功能,和豆包和deepSeek相差不大,每次会话传上条消息的id,传会话id
3、 图片上传在输入框上面,用的是自定义element-plus的上传组件el-upload,虽然有上传但会话没有显示图片,我还没处理好
4、在触发对话后会终止这个对话的请求,而且会触发失败接口,防止两个api一起返回抖动
5、自动显示在滚动条最底部

效果图

白色效果图

黑色效果图

前期准备

markdown解析器:
"marked": "^4.2.12",
markdown.ts markdown解析器
下面是不重要的插件:
"v-viewer": "^3.0.11", 图片预览用的这个
"element-plus": "^2.2.32",

// markdown.ts
import { marked } from "marked";
// import DOMPurify from "dompurify"; // 可选:安全净化

// 配置允许的HTML标签(根据需求调整)
const safeConfig = {
  ALLOWED_TAGS: [
    "h1",
    "h2",
    "h3",
    "h4",
    "strong",
    "em",
    "p",
    "br",
    "ul",
    "ol",
    "li",
  ],
  ALLOWED_ATTR: ["class", "style"],
};

/**
 * 解析 Markdown 文本为安全 HTML
 * @param content Markdown格式文本
 * @returns 安全HTML字符串
 */
export function parseMarkdown(content: string): string {
  // 创建自定义渲染器
  const renderer:any = new marked.Renderer();
  renderer.listitem = (text: string) => {
    return `<li class="cn-list-item">${text}</li>`;
  };

  // 启用marked的安全模式
  marked.setOptions({
    gfm: true, // 启用 GitHub Flavored Markdown
    breaks: true, // 禁用单换行转 <br>(保持原换行逻辑)
    pedantic: false, // 禁用严格模式(允许宽松的列表解析)
    silent: true, // 如果为 true,则解析器不会抛出任何异常或记录任何警告。任何错误都将作为字符串返回。
    renderer,
  });
  // 修复内容中的列表格式
  const fixedContent = content
    .replace(/^-\s+/gm, "- ") // 统一列表项格式
    .replace(/\n\s*-/g, "\n-"); // 修复多行列表

  return marked.parse(fixedContent) as string;

  // 解析Markdown并净化HTML
  // return DOMPurify.sanitize(marked.parse(content) as string, safeConfig);
}

请求封装

// webBreed.ts
import { parseMarkdown } from "@/utils/markdown"; // 确保你有这个解析器

// 存储AI分析的当前控制器
let currentController: AbortController | null = null;
// 存储当前请求的onError回调
let currentErrorCallback: ((error: Error) => void) | null = null;
// 中止AI分析请求的函数
export const abortAIAnalysisRequest = () => {
  if (currentController) {
    currentController.abort();
    // 手动触发onError回调,通知请求已中止
    if (currentErrorCallback) {
      currentErrorCallback(new Error("请求已手动中止")); // 传递明确的中止错误信息
    }
    currentController = null;
    currentErrorCallback = null;
    console.log("手动中止AI分析请求", currentController);
  }
};
// AI分析
export const SendAIAnalysisApi = async (
  data: {
    Animal?: string;
    Type?: string;
    Remark?: string;
    Photo?: string;
    Menu?: string;
    contextId?: string | number;
    SessionID?: string | number;
  },
  callbacks: {
    onProgress: (text: string, RespId?: any) => void;
    onComplete?: () => void;
    onError?: (error: Error) => void;
  }
) => {
  try {
    // 中止任何现有请求
    abortAIAnalysisRequest();

    // 创建新的请求控制器
    currentController = new AbortController();
    const { signal } = currentController;

    // 保存当前请求的onError回调
    currentErrorCallback = callbacks.onError || null;

    // 准备请求参数
    const problem = data.Remark || "";
    const token =
      Cookies.get("token") || localStorage.getItem("YooHooCowToken") || "";

    // 动态确定API地址
    let httpUrl = "";
    if (
      location.href.includes("localhost") ||
      location.href.indexOf("192.168.1.") !== -1
    ) {
      httpUrl = "http://192.168.1.11:8081";
    } else {
      httpUrl = window.location.origin;
    }

    // 构建请求URL和参数
    const url = `${httpUrl}${process.env.VUE_APP_BASE_API}/api/WebBreed/SendAIAnalysis`;
    const params = {
      breeds: data.Animal || "",
      type: data.Type || "",
      problem,
      imgUrl: data.Photo || "",
      menu: data.Menu || "",
      contextId: data.contextId || "",
      SessionID: data.SessionID || "",
    };

    // 发起请求
    const response = await fetch(url.toString(), {
      headers: { token, "Content-Type": "application/json" },
      method: "POST",
      body: JSON.stringify(params),
      signal,
    });
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    // 处理流式响应
    const reader = response.body?.getReader();
    if (!reader) throw new Error("No readable stream received");

    const decoder = new TextDecoder();
    let fullText = "";
    let RespId = "";
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        callbacks.onComplete?.();
        currentController = null;
        break;
      }
      const chunk = decoder.decode(value, { stream: true });
      // console.log("返回内容:", chunk);

      // 处理后端一下返回几个对象的问题
      if (chunk.indexOf("}{") > -1) {
        const jsonStrings: any = chunk.match(/\{.*?\}(?=\{|\s*$)/g);
        jsonStrings.forEach((json: any) => {
          let data = JSON.parse(json);
          if (data.Status === "OK") {
            fullText += data.Result;
            if (RespId === "") {
              RespId = data.RespId;
            }
          } else {
            fullText = data.Result;
          }
        });
      } else {
      	// 正常处理一个对象
        let data = JSON.parse(chunk);
        if (data.Status === "OK") {
          fullText += data.Result;
          if (RespId === "") {
            RespId = data.RespId;
          }
        } else {
          fullText = data.Result;
        }
      }
      // 解析并回调更新
      const parsedText = parseMarkdown(fullText);
      callbacks.onProgress(parsedText, RespId);
    }

    return fullText;
  } catch (error) {
    if (error instanceof Error && error.name !== "AbortError") {
      callbacks.onError?.(error);
    }
    currentController = null;
    currentErrorCallback = null;
    throw error;
  }
};

对话弹窗组件代码

<template>
    <div>
        <!-- AI正常对话的弹窗 -->
        <el-dialog :class="'AIDialoguePopup ' + theme" v-model="dialogVisible" :title="dialogTitle" width="1130px"
            destroy-on-close align-center center :append-to-body="false" draggable @close="close">
            <div class="AIDialoguePopupBox">
                <div class="AIDialoguePopupLeft">
                    <div class="Unfold" v-show="Unfold">
                        <div class="title">
                            <div class="logo">
                                <img :src="AiLogo" alt="logo">
                            </div>
                            <div class="name">
                                <span>智牧AI小助</span>
                            </div>
                            <div class="foldingImg">
                                <img :src="theme === 'dark' ? AiUnfoldIcon4Img : AiUnfoldIcon2Img" alt="Folding"
                                    @click="Unfold = false">
                            </div>
                        </div>
                        <div class="add" @click="addConversation">
                            <img :src="theme === 'dark' ? AiNewDialogueAddedIcon3Img : AiNewDialogueAddedIconImg"
                                alt="Add">
                            <span>新建对话</span>
                        </div>
                        <div class="answerHistory">
                            <div class="answerHistoryTitle">
                                <img :src="theme === 'dark' ? AiTimeIcon2Img : AiTimeIconImg" alt="Add">
                                <span>问答历史</span>
                            </div>
                            <div class="list">
                                <div class="item" :class="{ active: activeHistory === index }"
                                    v-for="(item, index) in AIAnalysisSessionList" :key="index"
                                    @click="GetAIAnalysisRecordList(item, index)">
                                    <span>{{ item.FirstProblem || '对话' }}</span>
                                    <el-icon @click.stop="DeleteAIAnalysisSession(item.ID)">
                                        <Close />
                                    </el-icon>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="Folding" v-show="!Unfold">
                        <img :src="theme === 'dark' ? AiUnfoldIcon3Img : AiUnfoldIcon1Img" alt="Folding"
                            @click="Unfold = true">
                        <img :src="theme === 'dark' ? AiNewDialogueAddedIcon2Img : AiNewDialogueAddedIconImg" alt="Add"
                            @click="addConversation">
                    </div>
                </div>
                <div class="AIDialoguePopupContent">
                    <div class="AIDialogCard" v-loading="fetchLoading" element-loading-text="深度思考中">
                        <div class="AIDialoguePopupChatList" v-if="chatList && chatList.length > 0">
                            <div class="item" :class="{ my: item.user === 1 }" v-for="(item, index) in chatList"
                                :key="index">
                                <!-- <div class="imgList" v-if="item.user === 1 && item.ImgUrl">
                                    <div class="imgItem" v-for="(imgItem, imgIndex) in item.ImgUrl.split(',')"
                                        :key="imgIndex">
                                        <img :src="imgItem" alt="img">
                                    </div>
                                </div> -->
                                <div class="chatDetails" v-html="item.Result">
                                </div>
                            </div>
                        </div>
                        <div class="nullData" v-else>
                            <img :src="MedianAiLogoImg" alt="logo">
                            <h2>智牧AI小助</h2>
                            <div class="subtitle">
                                <span>欢迎使用智牧AI小助,我可以为您答疑解惑</span>
                            </div>
                        </div>
                    </div>
                    <div class="inputBox">
                        <div class="fileList" v-if="fileList && fileList.length > 0">
                            <div class="fileItem" v-for="(item, index) in fileList" :key="index">
                                <img class="picture" :src="item.raw ? myCreateObjectURL(item.raw) : ''" alt="img" />
                                <div class="actions">
                                    <div class="preview" @click="handlePictureCardPreview(item.raw)">
                                        <el-icon><zoom-in /></el-icon>
                                    </div>
                                    <div class="delete" @click="handleRemove(item.raw)">
                                        <el-icon>
                                            <Delete />
                                        </el-icon>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <div class="inputContent">
                            <textarea v-model="form.Remark" rows="3" maxlength="1000"
                                placeholder="请输入问题,小助将为您解答(可输入1000字)" @keyup.enter="toDiagnosis"></textarea>
                        </div>
                        <div class="uploadOrBtn">
                            <div class="upload">
                                <el-upload ref="uploadRef" v-model:file-list="fileList" :action="action"
                                    :headers="headers" :auto-upload="false" :limit="3" :multiple="true"
                                    :show-file-list="false" accept="image/png,image/jpg,image/jpeg"
                                    :on-exceed="handleExceed" :on-success="handleAvatarSuccess" :on-error="handleError"
                                    :on-remove="handleRemove" :before-upload="beforeUpload">
                                    <template #trigger>
                                        <img :src="theme === 'dark' ? AiPictureIcon2Img : AiPictureIconImg"
                                            alt="upload">
                                    </template>
                                </el-upload>
                            </div>
                            <div class="submit" @click="toDiagnosis">
                                <img :src="theme === 'dark' ? AiSendIcon2Img : AiSendIconImg" alt="submit">
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </el-dialog>
    </div>
</template>

<script lang="ts" setup>
import { SendAIAnalysisApi, abortAIAnalysisRequest, GetAIAnalysisSessionListApi, GetAIAnalysisRecordListApi, SaveAIAnalysisSessionApi, DeleteAIAnalysisSessionApi } from "@/api/webBreed"

import AiLogo from "/public/commonPage/Home/homeContent/AiLogo.png"
import MedianAiLogoImg from "/public/commonPage/Home/AI/MedianAiLogo.png"
import AiPictureIconImg from "/public/commonPage/Home/AI/AiPictureIcon.png"
import AiSendIconImg from "/public/commonPage/Home/AI/AiSendIcon.png"
import AiTimeIconImg from "/public/commonPage/Home/AI/AiTimeIcon.png"
import AiUnfoldIcon1Img from "/public/commonPage/Home/AI/AiUnfoldIcon1.png"
import AiUnfoldIcon2Img from "/public/commonPage/Home/AI/AiUnfoldIcon2.png"
import AiNewDialogueAddedIconImg from "/public/commonPage/Home/AI/AiNewDialogueAddedIcon.png"


import AiPictureIcon2Img from "/public/commonPage/Home/AI/AiPictureIcon2.png"
import AiSendIcon2Img from "/public/commonPage/Home/AI/AiSendIcon2.png"
import AiTimeIcon2Img from "/public/commonPage/Home/AI/AiTimeIcon2.png"
import AiUnfoldIcon3Img from "/public/commonPage/Home/AI/AiUnfoldIcon3.png"
import AiUnfoldIcon4Img from "/public/commonPage/Home/AI/AiUnfoldIcon4.png"
import AiNewDialogueAddedIcon2Img from "/public/commonPage/Home/AI/AiNewDialogueAddedIcon2.png"
import AiNewDialogueAddedIcon3Img from "/public/commonPage/Home/AI/AiNewDialogueAddedIcon3.png"

import {
    ref, reactive, watch, computed, Ref, nextTick,
    getCurrentInstance
} from "vue";

import { ElMessage } from "element-plus";
import { Close, Delete, ZoomIn } from '@element-plus/icons-vue'
import type { UploadProps, UploadUserFile } from 'element-plus'
import Cookies from "js-cookie";

let emit = defineEmits(["changeShowAIDialoguePopup"]);
let props = defineProps<{
    show: boolean;
    theme?: string;
}>();

// 弹窗信息
let dialogVisible = ref<boolean>(props.show || false);
watch(
    () => props.show,
    (newVal) => {
        dialogVisible.value = newVal;
        GetAIAnalysisSessionList()
    }
);
let dialogTitle = ref<string>("");
let theme = ref<string>(props.theme || "default");
// 是否对话中
let fetchLoading = ref<boolean>(false);
// 展开侧边栏
let Unfold = ref<boolean>(true);

// 当前对话详情列表
let chatList = ref<any[]>([
    // {
    //     Result: '欢迎使用AI养殖超能小助,请问有什么能帮助您的吗?',
    //     ImgUrl: '',
    //     user: 0
    // }
]);

// 对话表单
let form = reactive({
    Remark: '',
    Photo: '',
    // 上下文id
    contextId: '',
    // 会话id
    SessionID: ''
})

// 上传列表和上传中状态
const fileList = ref<any[]>([]);
const uploading = ref(false);

// 图片上传
const VUE_APP_BASE_API = process.env.VUE_APP_BASE_API;
let action = ref(
    VUE_APP_BASE_API + "/api/WebBreed/UploadFile?flowName=PastureMap"
);
// 上传参数请求头
let headers = reactive({
    token: Cookies.get("token") || "",
});
const uploadRef: Ref = ref(null);

// 上传前-限制文件大小和类型
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
    const isJPGorPNG = file.type === 'image/jpeg' || file.type === 'image/png';
    const isLt5M = file.size / 1024 / 1024 < 5;
    if (!isJPGorPNG) {
        ElMessage.error('只能上传JPG/PNG格式的图片!');
        return false;
    }
    if (!isLt5M) {
        ElMessage.error('图片大小不能超过5MB!');
        return false;
    }
    return true;
};
// 处理超出限制
const handleExceed: UploadProps['onExceed'] = (files) => {
    ElMessage.warning(`最多只能上传3张图片,当前选择了${files.length}`);
};
// 上传成功
const handleAvatarSuccess: UploadProps["onSuccess"] = (res, file) => {
    console.log('上传成功', res, file, fileList.value);
    // 所有文件上传完成后,调用AI接口
    if (fileList.value.every(file => file.status === 'success')) {
        form.Photo = fileList.value.map((item: any) => item.response?.resultdata?.[0]).join(',')
        handleDetails();
        uploading.value = false;
    }
};
// 上传失败
const handleError = () => {
    uploading.value = false;
    ElMessage.warning("上传失败!");
};
// 删除
const handleRemove = (file: any) => {
    fileList.value = fileList.value.filter(item => item.uid !== file.uid);
};

// 预览
// v-viewer 查看图片
const viewerApi = (getCurrentInstance() as any).appContext.config
    .globalProperties.$viewerApi;
const showImagesInViewer = (images: string) => {
    if (images) {
        if (images.indexOf(",") !== -1) {
            viewerApi({ images: images.split(",") });
        } else {
            viewerApi({ images: [images] });
        }
    }
};
const handlePictureCardPreview = (file: any) => {
    showImagesInViewer(myCreateObjectURL(file));
}

// 图片临时路径
const myCreateObjectURL = (file: any) => {
    if (file) {
        return URL.createObjectURL(file);
    } else {
        return "";
    }
}

// 滚动到底部
const scrollToBottom = () => {
    const chatListDome = document.querySelector('.AIDialoguePopupChatList');
    if (chatListDome) {
        // chatList.scrollTop = chatList.scrollHeight; // 直接滚动到最底部
        // 或者用平滑滚动:
        chatListDome.scrollTo({
            top: chatListDome.scrollHeight,
            behavior: 'smooth'
        });
    }
};

// 发送AI对话
const handleDetails = async () => {
    fetchLoading.value = true
    if (form.Remark) {
        form.Remark = form.Remark.replace(/[\r\n]/g, "")
    }
    if (chatList.value && chatList.value.length <= 0) {
        await SaveAIAnalysisSession()
    }
    chatList.value.push({
        Result: form.Remark,
        user: 1
    })
    let params = {
        Animal: '牛',
        Type: '自由问答',
        Menu: '',
        Remark: form.Remark || '',
        Photo: form.Photo || '',
        contextId: form.contextId || '',
        SessionID: form.SessionID || '',
    }
    form.Remark = ''
    form.Photo = ''
    fileList.value = []
    await SendAIAnalysisApi(params,
        {
            onProgress: (text, RespId) => {
                if (fetchLoading.value) {
                    fetchLoading.value = false
                }
                if (chatList.value[chatList.value.length - 1].user !== 0) {
                    chatList.value.push({
                        Result: text,
                        user: 0,
                    })
                } else {
                    chatList.value[chatList.value.length - 1].Result = text
                }
                form.contextId = RespId || ''
                nextTick(() => {
                    scrollToBottom();
                });
            },
            onComplete: () => {
                fetchLoading.value = false
                nextTick(() => {
                    scrollToBottom();
                });
            },
            onError: (error) => {
                fetchLoading.value = false
            }
        })
}
// 点击发送
const toDiagnosis = async () => {
    if (fetchLoading.value) {
        ElMessage.error('请先等小助回答完成!');
        return
    }
    abortAIAnalysisRequest()
    if (form.Remark === '') {
        ElMessage.error('请输入问题!');
        return
    }
    uploading.value = true;
    try {
        if (fileList.value.length > 0) {
            // 手动触发上传
            uploadRef.value!.submit();
        } else {
            // 如果没有图片,直接调用AI接口
            await handleDetails();
        }
    } catch (error) {
        uploading.value = false;
    }
}
// 关闭对话弹窗
const close = () => {
    abortAIAnalysisRequest()
    fetchLoading.value = false
    emit("changeShowAIDialoguePopup", false);
    addConversation()
}

// 会话列表
let AIAnalysisSessionList = ref<any[]>([])
let activeHistory = ref<number>(-1)

// 获取会话列表
const GetAIAnalysisSessionList = () => {
    GetAIAnalysisSessionListApi({
        page: 1,
        rows: 999,
    }).then(res => {
        AIAnalysisSessionList.value = res.Data || []
    })
}

// 获取当前对话详情列表
const GetAIAnalysisRecordList = (info: any, index: number) => {
    activeHistory.value = index
    GetAIAnalysisRecordListApi({
        page: 1,
        rows: 99,
        // sord: 'DESC',
        SessionID: info.ID || ''
    }).then(res => {
        chatList.value = []
        if (res.Data && res.Data.length > 0) {
            form.contextId = res.Data[0].ContextId || ''
            form.SessionID = res.Data[0].SessionID || ''

            res.Data.forEach((item: any) => {
                chatList.value.unshift({
                    Result: item.Problem || '',
                    ImgUrl: item.ImgUrl || '',
                    user: 1
                }, {
                    Result: item.Result || '',
                    user: 0
                })

                nextTick(() => {
                    scrollToBottom();
                });
            })
        }
    })
}

// 新建一个会话--重置会话信息
const addConversation = () => {
    chatList.value = []
    form.contextId = ''
    form.SessionID = ''
    activeHistory.value = -1
}
const SaveAIAnalysisSession = async () => {
    let res = await SaveAIAnalysisSessionApi({})
    if (res.ResultCode === 200) {
        // console.log('新建一个会话', res);
        form.SessionID = res.Data.ID || ''
    }
}

// 删除会话
const DeleteAIAnalysisSession = (id: number) => {
    DeleteAIAnalysisSessionApi(id).then(res => {
        if (res.ResultCode === 200) {
            GetAIAnalysisSessionList()
            if (id === +form.SessionID) {
                addConversation()
            }
        }
    })
}
</script>

<style lang="less" scoped>
:deep(.el-dialog.AIDialoguePopup) {
    position: relative;
    border-radius: 35px;
    background-image: url("/public/commonPage/Home/AI/AIDialoguePopup.png");
    background-size: 100% 100%;

    .el-dialog__header {
        position: absolute;
        z-index: 3;
        top: 0;
        left: 0;
        width: 100%;
        background-color: transparent;
        margin-right: 0;

        .el-dialog__title {
            color: #409eff;
            font-weight: bold;
            font-size: 30px;
        }

        .el-dialog__headerbtn {
            width: 24px;
            height: 24px;
            top: 18px;
            right: 26px;

            &::after {
                content: '';
                position: absolute;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                background-image: url('/public/commonPage/Home//AI/AiDelIcon.png');
                background-size: 100% 100%;
            }

            .el-dialog__close {
                display: none;
            }
        }
    }

    .el-dialog__body {
        padding: 0;

        .AIDialoguePopupBox {
            position: relative;
            display: flex;

            .AIDialoguePopupLeft {
                .Unfold {
                    padding: 16px 10px 4px 15px;
                    width: 180px;
                    height: 695px;
                    border-right: 1px solid #73DCFF;
                    font-family: PingFang SC;
                    transition: all 0.5s;

                    &::after {
                        content: "";
                        position: absolute;
                        left: 17px;
                        bottom: 4px;
                        width: 150px;
                        height: 150px;
                        background-image: url("/public/commonPage/Home/AI/MedianAiLogo.png");
                        background-size: 100% 100%;
                    }

                    .title {
                        padding-right: 10px;
                        display: flex;
                        align-items: center;
                        justify-content: space-between;

                        img {
                            display: block;
                            width: 100%;
                            height: 100%;
                        }

                        .logo {
                            width: 32px;
                            height: 32px;
                        }

                        .name {
                            font-weight: bold;
                            font-size: 16px;
                            color: #091A34;
                        }

                        .foldingImg {
                            width: 14px;
                            height: 14px;
                            cursor: pointer;
                        }
                    }

                    .add {
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        width: 155px;
                        height: 30px;
                        // background: #CCDEFF;
                        border-radius: 4px;
                        margin: 8px 0 16px;
                        font-weight: 400;
                        font-size: 14px;
                        color: #091A34;
                        cursor: pointer;

                        img {
                            display: block;
                            width: 24px;
                            height: 24px;
                            margin-right: 5px;
                        }
                    }

                    .answerHistory {
                        padding: 0 5px 0 0;

                        .answerHistoryTitle {
                            display: flex;
                            align-items: center;
                            font-weight: 400;
                            font-size: 14px;
                            color: #091A34;
                            margin-bottom: 14px;

                            img {
                                display: block;
                                width: 14px;
                                height: 14px;
                                margin-right: 5px;
                            }
                        }

                        .list {
                            max-height: 425px;
                            overflow-y: auto;

                            &::-webkit-scrollbar {
                                width: 5px;
                                height: 5px;
                                background: transparent;
                            }

                            &::-webkit-scrollbar-track,
                            &-small::-webkit-scrollbar-track {
                                border-radius: 10px;
                                background: transparent;
                            }

                            &::-webkit-scrollbar-thumb,
                            &-small::-webkit-scrollbar-thumb {
                                border-radius: 5px;
                                background-color: #d9d9d9;
                                cursor: pointer;
                            }

                            .item {
                                display: flex;
                                align-items: center;
                                padding: 5px 10px;
                                cursor: pointer;
                                font-weight: 400;
                                font-size: 14px;
                                color: #000000;
                                border-radius: 4px;
                                margin-bottom: 3px;

                                &:last-child {
                                    margin-bottom: 0;
                                }

                                &:hover,
                                &.active {
                                    background: #CCDEFF;
                                }

                                &:hover {
                                    .el-icon {
                                        display: block;
                                    }
                                }

                                span {
                                    flex: 1;
                                    display: -webkit-box;
                                    -webkit-box-orient: vertical;
                                    -webkit-line-clamp: 1;
                                    overflow: hidden;
                                }

                                .el-icon {
                                    display: none;
                                }
                            }
                        }
                    }
                }

                .Folding {
                    position: absolute;
                    left: 24px;
                    top: 24px;
                    transition: all 0.5s;

                    img {
                        width: 24px;
                        height: 24px;
                        cursor: pointer;
                        margin-right: 8px;

                        &:last-child {
                            margin-right: 0;
                        }
                    }
                }
            }

            .AIDialoguePopupContent {
                flex: 1;
                display: flex;
                flex-direction: column;
                height: 695px;
                padding: 64px 50px 46px;

                .AIDialogCard {
                    // height: 60vh;
                    flex: 1;
                    min-height: 0;

                    .AIDialoguePopupChatList {
                        height: 100%;
                        overflow-y: auto;
                        // padding: 0 2px 0 0;
                        padding: 0 53px;

                        &::-webkit-scrollbar {
                            width: 5px;
                            height: 5px;
                            background: transparent;
                        }

                        &::-webkit-scrollbar-track,
                        &-small::-webkit-scrollbar-track {
                            border-radius: 10px;
                            background: transparent;
                        }

                        &::-webkit-scrollbar-thumb,
                        &-small::-webkit-scrollbar-thumb {
                            border-radius: 5px;
                            background-color: #d9d9d9;
                            cursor: pointer;
                        }

                        .item {
                            display: flex;
                            // flex-direction: column;
                            margin-bottom: 15px;

                            .imgList {
                                display: flex;
                                margin-bottom: 10px;

                                .imgItem {
                                    width: 40px;
                                    height: 40px;
                                    border: 1px solid #fff;
                                    border-radius: 5px;
                                    cursor: pointer;
                                    margin-right: 10px;
                                    &:last-child {
                                        margin-right: 0;
                                    }

                                    img {
                                        width: 100%;
                                        height: 100%;
                                    }
                                }
                            }

                            .chatDetails {
                                flex: 1;
                                // background-color: rgba(255, 255, 255, .7);
                                color: #091A34;
                                padding: 5px 20px;
                                border-radius: 20px;
                                font-size: 16px;
                                line-height: 1.5;
                                font-family: 'PingFang SC';

                                h1 {
                                    font-size: 32px;
                                    font-weight: bold;
                                    margin: 6px 0;
                                }

                                h2 {
                                    font-size: 24px;
                                    font-weight: bold;
                                    margin: 6px 0;
                                }

                                h3 {
                                    font-size: 20px;
                                    font-weight: bold;
                                    margin: 6px 0;
                                }

                                h4 {
                                    font-size: 16px;
                                    font-weight: bold;
                                    margin: 6px 0;
                                }

                                h5 {
                                    font-size: 13.28px;
                                    font-weight: bold;
                                    margin: 6px 0;
                                }

                                h6 {
                                    font-size: 12px;
                                    font-weight: bold;
                                    margin: 6px 0;
                                }

                                /* 中文风格列表 */
                                .cn-list-item {
                                    list-style: none;
                                    /* 隐藏默认符号 */
                                    position: relative;
                                    padding-left: 1.2em;
                                    /* 留出符号空间 */
                                    line-height: 1.5;

                                    &::before {
                                        content: "·";
                                        /* 中文圆点符号 */
                                        position: absolute;
                                        left: 0;
                                        font-weight: bold;
                                        font-size: 1em;
                                        color: #333;
                                    }
                                }

                                /* 一级列表用 · */
                                .cn-list-item::before {
                                    content: "·";
                                }

                                /* 二级列表用 ▪ */
                                ul ul .cn-list-item::before {
                                    content: "▪";
                                }

                                /* 三级列表用 ▫ */
                                ul ul ul .cn-list-item::before {
                                    content: "▫";
                                }

                            }

                            &.my {
                                width: auto;
                                justify-content: flex-end;
                                // flex-direction: column;

                                .imgList {
                                    
                                }

                                .chatDetails {
                                    flex: initial;
                                    background-color: #CCDEFF;
                                    font-size: 16px;
                                    color: #091A34;
                                }
                            }
                        }
                    }

                    .nullData {
                        width: 100%;
                        height: 100%;
                        display: flex;
                        flex-direction: column;
                        justify-content: center;
                        align-items: center;

                        img {
                            width: 234px;
                            height: 234px;
                        }

                        h2 {
                            font-weight: bold;
                            font-size: 28px;
                            color: #091A34;
                            margin-bottom: 14px;
                        }

                        .subtitle {
                            font-weight: 400;
                            font-size: 14px;
                            color: rgba(9, 26, 52, 0.8);
                        }
                    }
                }

                .inputBox {
                    flex-shrink: 0;
                    display: flex;
                    flex-direction: column;
                    margin: 10px auto 0;
                    width: 760px;
                    background: rgba(255, 255, 255, 0.4);
                    box-shadow: 0px 6px 10px 0px rgba(0, 0, 0, 0.1);
                    border: 2px solid #F4F8FF;
                    border-radius: 16px;

                    .fileList {
                        display: flex;
                        padding: 12px 10px 5px;

                        .fileItem {
                            position: relative;
                            width: 52px;
                            height: 52px;
                            border-radius: 10px;
                            overflow: hidden;
                            margin-right: 10px;

                            &:hover {
                                .actions {
                                    opacity: 1;
                                }
                            }

                            .picture {
                                width: 100%;
                                height: 100%;
                                object-fit: cover;
                            }

                            .actions {
                                position: absolute;
                                width: 100%;
                                height: 100%;
                                left: 0;
                                top: 0;
                                cursor: default;
                                display: inline-flex;
                                justify-content: center;
                                align-items: center;
                                opacity: 0;
                                background-color: rgba(0, 0, 0, 0.5);
                                color: #fff;
                                font-size: 16px;
                                transition: opacity 0.3s;

                                .preview {
                                    cursor: pointer;
                                    margin-right: 4px;
                                }

                                .delete {
                                    cursor: pointer;
                                }
                            }
                        }
                    }

                    .inputContent {
                        flex: 1;

                        textarea {
                            width: 100%;
                            height: 100%;
                            padding: 10px 12px 8px;
                            outline: none;
                            border: none;
                            border-radius: 4px;
                            font-size: 14px;
                            line-height: 1.5;
                            box-sizing: border-box;
                            // 允许垂直调整大小
                            // resize: vertical;
                            // 隐藏滚动条
                            scrollbar-width: none;
                            -ms-overflow-style: none;

                            &:focus {
                                outline: none;
                                border: none;
                            }
                        }
                    }

                    .uploadOrBtn {
                        padding: 10px 12px 15px;
                        display: flex;
                        justify-content: flex-end;

                        .upload,
                        .submit {
                            width: 24px;
                            height: 24px;
                            cursor: pointer;

                            img {
                                width: 100%;
                                height: 100%;
                            }
                        }

                        .upload {
                            position: relative;
                            margin-right: 17px;

                            &::after {
                                content: '';
                                position: absolute;
                                top: 50%;
                                right: -8.5px;
                                transform: translate(0, -50%);
                                width: 1px;
                                height: 16px;
                                background: rgba(192, 192, 192, 0.5);
                            }
                        }
                    }
                }
            }
        }
    }


    &.dark {
        background-image: url("/public/commonPage/Home/AI/AIDialoguePopup2.png");
        background-size: 100% 100%;
        border-radius: 45px;

        .el-dialog__header {
            .el-dialog__headerbtn {
                &::after {
                    background-image: url('/public/commonPage/Home/AI/AiDelIcon2.png');
                }
            }
        }

        .el-dialog__body {
            .AIDialoguePopupBox {
                .AIDialoguePopupLeft {
                    .Unfold {
                        border-right: 1px solid #2B8CE7;

                        .title {
                            .name {
                                color: #FFFFFF;
                            }
                        }

                        .add {
                            background: linear-gradient(180deg, #2B3EE7 0%, #2B8CE7 100%);
                            color: #FFFFFF;

                            img {
                                width: 14px;
                                height: 14px;
                            }
                        }

                        .answerHistory {
                            .answerHistoryTitle {
                                color: #fff;
                            }

                            .list {

                                &::-webkit-scrollbar-thumb,
                                &-small::-webkit-scrollbar-thumb {
                                    background-color: #2b8ce7;
                                    ;
                                }

                                .item {
                                    color: #fff;

                                    &:hover,
                                    &.active {
                                        background: #2B8CE7;
                                    }
                                }
                            }
                        }
                    }

                    .Folding {
                        position: absolute;
                        left: 24px;
                        top: 24px;
                        transition: all 0.5s;

                        img {
                            width: 24px;
                            height: 24px;
                            cursor: pointer;
                            margin-right: 8px;

                            &:last-child {
                                margin-right: 0;
                            }
                        }
                    }
                }

                .AIDialoguePopupContent {
                    .AIDialogCard {
                        .AIDialoguePopupChatList {

                            &::-webkit-scrollbar-thumb,
                            &-small::-webkit-scrollbar-thumb {
                                background-color: #2b8ce7;
                            }

                            .item {
                                .chatDetails {
                                    color: #FFFFFF;

                                    .cn-list-item {
                                        &::before {
                                            color: #ffffff;
                                        }
                                    }
                                }

                                &.my {
                                    .chatDetails {
                                        background-color: #FFFFFF;
                                        color: #091A34;
                                    }
                                }
                            }
                        }

                        .nullData {
                            h2 {
                                color: #FFFFFF;
                            }

                            .subtitle {
                                color: rgba(255, 255, 255, 0.8);
                            }
                        }
                    }

                    .inputBox {
                        position: relative;
                        background: #212867;
                        border: none;
                        border-radius: 8px;

                        &::before {
                            content: "";
                            position: absolute;
                            top: -2px;
                            left: -2px;
                            right: -2px;
                            bottom: -2px;
                            z-index: -1;
                            border-radius: 8px;
                            background: linear-gradient(180deg, #2b3ee7, #2b8ce7);
                        }

                        .fileList {}

                        .inputContent {
                            textarea {
                                background-color: transparent;
                                color: #FFFFFF;

                                &::placeholder {
                                    color: rgba(255, 255, 255, 0.5);
                                }
                            }
                        }

                        .uploadOrBtn {
                            .upload {

                                &::after {
                                    background: rgba(0, 157, 255, 1);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
</style>

使用组件

// show显示 theme主题不传白色 dark暗色 changeShowAIDialoguePopup关闭回调
<AIDialoguePopup :show="AIDialoguePopupShow" theme="dark" @changeShowAIDialoguePopup="AIDialoguePopupShow = false" />

import AIDialoguePopup from "@/components/AIDialoguePopup/Index.vue"

let AIDialoguePopupShow = ref(false)
const dblClickAi = () => {
  // 显示对话聊天组件
  AIDialoguePopupShow.value = true
}
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐