|
|
@@ -0,0 +1,816 @@
|
|
|
+<template>
|
|
|
+ <!-- 图生图 -->
|
|
|
+ <div class="home-container">
|
|
|
+ <!-- 展开收起侧边栏 -->
|
|
|
+ <div
|
|
|
+ class="icon-expand"
|
|
|
+ :style="{
|
|
|
+ backgroundColor: drawerVisible ? '#44449c' : '#7F70C840',
|
|
|
+ left: drawerVisible ? '18%' : '0'
|
|
|
+ }"
|
|
|
+ @click="toggleDrawer"
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ class="vertical-lines"
|
|
|
+ :style="{
|
|
|
+ color: drawerVisible ? '#8a78d0' : 'white'
|
|
|
+ }"
|
|
|
+ >||</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 左侧折叠面板 -->
|
|
|
+ <LeftPanel ref="leftPanelRef" v-if="drawerVisible"/>
|
|
|
+
|
|
|
+ <div class="left-group2">
|
|
|
+ <div class="title-box">
|
|
|
+ <div class="box-icon" @click="goBack">
|
|
|
+ <el-icon class="left-icon"><ArrowLeftBold /></el-icon>
|
|
|
+ 图生图
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="img-box">
|
|
|
+ <p>
|
|
|
+ <img
|
|
|
+ style=" width: fit-content; height: 180px; margin: 10px;"
|
|
|
+ src="@/assets/images/color.png"
|
|
|
+ class="avatar user"
|
|
|
+ />
|
|
|
+ </p>
|
|
|
+ <p>期待你的画作喔~</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧AI问答 -->
|
|
|
+ <div class="number-people">
|
|
|
+ <div class="content-box">
|
|
|
+ <!-- AI对话框 -->
|
|
|
+ <div class="chat-dialog">
|
|
|
+ <!-- 对话消息列表 -->
|
|
|
+ <div class="message-list">
|
|
|
+ <div v-if="imageAllList.length > 0">
|
|
|
+ <div v-for="(item, index) in imageAllList" :key="index">
|
|
|
+ <!-- 用户消息 -->
|
|
|
+ <div class="user-message" v-if="item.type === 'user'">
|
|
|
+ {{ item.content }}
|
|
|
+ <div class="user-image-list" v-if="item.imageUrl">
|
|
|
+ <el-image
|
|
|
+ style="width: fit-content; height: 180px; margin: 10px;"
|
|
|
+ :src="item.imageUrl"
|
|
|
+ :preview-src-list="[item.imageUrl]"
|
|
|
+ fit="cover"
|
|
|
+ show-progress
|
|
|
+ >
|
|
|
+ <template
|
|
|
+ #toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }"
|
|
|
+ >
|
|
|
+ <el-icon @click="prev"><Back /></el-icon>
|
|
|
+ <el-icon @click="next"><Right /></el-icon>
|
|
|
+ <el-icon @click="setActiveItem(item.imageList.length - 1)">
|
|
|
+ <DArrowRight />
|
|
|
+ </el-icon>
|
|
|
+ <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
|
|
|
+ <el-icon @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })"><ZoomIn /></el-icon>
|
|
|
+ <el-icon @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })"><RefreshRight /></el-icon>
|
|
|
+ <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
|
|
|
+ <el-icon @click="reset"><Refresh /></el-icon>
|
|
|
+ <el-icon @click="download(activeIndex)"><Download /></el-icon>
|
|
|
+ </template>
|
|
|
+ </el-image>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- AI生成图片对话框 -->
|
|
|
+ <div class="ai-message" v-if="item.type !== 'user'">
|
|
|
+ {{ item.content }}
|
|
|
+ <div class="image-list" v-if="item.imageList">
|
|
|
+ <el-image
|
|
|
+ v-for="(image, index) in item.imageList"
|
|
|
+ :key="index"
|
|
|
+ style=" width: fit-content; height: 220px; margin: 10px;"
|
|
|
+ :src="image"
|
|
|
+ :preview-src-list="item.imageList"
|
|
|
+ fit="cover"
|
|
|
+ show-progress
|
|
|
+ >
|
|
|
+ <template
|
|
|
+ #toolbar="{ actions, reset, activeIndex}"
|
|
|
+ >
|
|
|
+ <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
|
|
|
+ <el-icon
|
|
|
+ @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })">
|
|
|
+ <ZoomIn />
|
|
|
+ </el-icon>
|
|
|
+ <el-icon
|
|
|
+ @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })">
|
|
|
+ <RefreshRight />
|
|
|
+ </el-icon>
|
|
|
+ <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
|
|
|
+ <el-icon @click="reset"><Refresh /></el-icon>
|
|
|
+ <el-icon @click="download(activeIndex)"><Download /></el-icon>
|
|
|
+ </template>
|
|
|
+ </el-image>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 输入框和发送按钮 -->
|
|
|
+ <div class="input-section">
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ v-model="inputMessage"
|
|
|
+ placeholder="描述任何画面..."
|
|
|
+ @keyup.enter="sendMessage"
|
|
|
+ style="flex: 1; margin-right: 8px;"
|
|
|
+ />
|
|
|
+ <!-- 参考图 -->
|
|
|
+ <ImageUpload v-model="uploadedImage" ref="imageUploadRef"/>
|
|
|
+ <!-- 语音输入按钮 -->
|
|
|
+ <button
|
|
|
+ @click="toggleSpeechInput"
|
|
|
+ class="speech-btn"
|
|
|
+ :class="{ 'recording': isRecording }"
|
|
|
+ >
|
|
|
+ <el-icon v-if="!isRecording"><Microphone /></el-icon>
|
|
|
+ <el-icon v-else><Mute /></el-icon>
|
|
|
+ <!-- 显示倒计时(仅录音时显示) -->
|
|
|
+ <span v-if="isRecording" class="countdown-text">{{ countdown }}s</span>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <!-- 终止按钮 -->
|
|
|
+ <div
|
|
|
+ v-if="conversationInProgress"
|
|
|
+ @click="stopStream"
|
|
|
+ class="stop-btn"
|
|
|
+ title="终止问答"
|
|
|
+ >
|
|
|
+ <img :src="stopicon" alt="停止" />
|
|
|
+ </div>
|
|
|
+ <button v-if="!conversationInProgress"
|
|
|
+ @click="sendMessage">发送</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, onMounted,onUnmounted} from 'vue'
|
|
|
+import {AiImageStatusEnum, CreatePainting, PaintingGetMys} from '@/api/questions.js'
|
|
|
+import { useRouter, useRoute } from 'vue-router'
|
|
|
+import NumberPeople00 from '@/assets/images/xiaozhi.png'
|
|
|
+import {
|
|
|
+ Document,
|
|
|
+ Menu as IconMenu,
|
|
|
+ Location,
|
|
|
+ Setting,
|
|
|
+ ArrowLeftBold,
|
|
|
+ Fold,
|
|
|
+ Expand,
|
|
|
+ ChatLineRound,
|
|
|
+ Picture,
|
|
|
+ MagicStick,
|
|
|
+ Tickets,
|
|
|
+ User
|
|
|
+} from '@element-plus/icons-vue'
|
|
|
+
|
|
|
+import { saveRecord } from '@/api/personalized/index.js'
|
|
|
+
|
|
|
+// 导入全局状态
|
|
|
+import { globalState } from '@/utils/globalState.js'
|
|
|
+// 语音图标
|
|
|
+import { Microphone, Mute } from "@element-plus/icons-vue";
|
|
|
+// 终止按钮
|
|
|
+import stopicon from "@/assets/icon/stopicon.png";
|
|
|
+// 消息组件
|
|
|
+import {Message} from "@/utils/message/Message.js";
|
|
|
+
|
|
|
+// 图生图
|
|
|
+import ImageUpload from '@/components/ImageUpload/index.vue';
|
|
|
+
|
|
|
+// 导入getModelIdByType接口
|
|
|
+import { getModelIdByType } from '@/api/teachers.js'
|
|
|
+import { ModelTypeEnum } from '@/api/teachers.js'
|
|
|
+
|
|
|
+// 存储上传的图片
|
|
|
+const uploadedImage = ref('');
|
|
|
+
|
|
|
+const imageUploadRef = ref(null);
|
|
|
+
|
|
|
+// 语音输入响应式变量
|
|
|
+const isRecording = ref(false); // 录音状态
|
|
|
+const recognition = ref(null); // 语音识别实例
|
|
|
+const countdown = ref(0); // 倒计时剩余秒数
|
|
|
+const countdownTimer = ref(null); // 倒计时定
|
|
|
+// 对话状态变量
|
|
|
+const conversationInProgress = ref(false); // 对话是否正在进行中
|
|
|
+const conversationInAbortController = ref(); // 对话进行中 abort 控制器
|
|
|
+
|
|
|
+
|
|
|
+// 返回上一页
|
|
|
+const goBack = () => {
|
|
|
+ router.push('/ai-laboratory')
|
|
|
+}
|
|
|
+const router = useRouter()
|
|
|
+const route = useRoute()
|
|
|
+
|
|
|
+// 导入图片
|
|
|
+import question from '@/assets/icon/question.png'
|
|
|
+import painting from '@/assets/icon/painting.png'
|
|
|
+import human from '@/assets/icon/human.png'
|
|
|
+
|
|
|
+import LeftPanel from '@/components/LeftPanel.vue'
|
|
|
+const leftPanelRef = ref(null)
|
|
|
+
|
|
|
+
|
|
|
+// tts 语音
|
|
|
+import { useAudioPlayer } from '@/api/tts/useAudioPlayer';
|
|
|
+const { playAudioChunk } = useAudioPlayer();
|
|
|
+
|
|
|
+// 添加抽屉显示状态
|
|
|
+const drawerVisible = ref(true)
|
|
|
+// 添加切换抽屉显示状态的函数
|
|
|
+const toggleDrawer = () => {
|
|
|
+ drawerVisible.value = !drawerVisible.value
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ // 年级ID相关
|
|
|
+const gradeId = ref('')
|
|
|
+// 添加消息计数器变量
|
|
|
+const messageCount = ref(0)
|
|
|
+// modelId响应式变量
|
|
|
+const modelId = ref(0)
|
|
|
+// 保存记录
|
|
|
+onMounted(async () => {
|
|
|
+ // 从全局状态初始化年级ID
|
|
|
+ gradeId.value = globalState.initGradeId()
|
|
|
+ try{
|
|
|
+ const res = await saveRecord({
|
|
|
+ brpNjId: gradeId.value,
|
|
|
+ brpType: "aiCount",
|
|
|
+ brpProgress: 1
|
|
|
+ });
|
|
|
+ // 获取modelId
|
|
|
+ const modelRes = await getModelIdByType({ type: ModelTypeEnum.IMAGE_TO_IMAGE, platform: "DouBao" })
|
|
|
+ modelId.value = modelRes.data
|
|
|
+ }catch(error){
|
|
|
+ console.error('保存记录失败:', error);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+// 消息列表和输入内容的响应式变量
|
|
|
+const messages = ref([])
|
|
|
+const inputMessage = ref('')
|
|
|
+
|
|
|
+// 初始化语音识别
|
|
|
+const initSpeechRecognition = () => {
|
|
|
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
|
+ if (!SpeechRecognition) {
|
|
|
+ alert("当前浏览器不支持语音输入功能");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const instance = new SpeechRecognition();
|
|
|
+ instance.lang = 'zh-CN';
|
|
|
+ instance.interimResults = false;
|
|
|
+
|
|
|
+ instance.onresult = (event) => {
|
|
|
+ if (event.results?.[0]?.[0]) {
|
|
|
+ inputMessage.value += event.results[0][0].transcript;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 识别器结束时清除定时器
|
|
|
+ instance.onend = () => {
|
|
|
+ clearInterval(countdownTimer.value);
|
|
|
+ isRecording.value = false;
|
|
|
+ countdown.value = 0;
|
|
|
+ };
|
|
|
+
|
|
|
+ instance.onerror = (event) => {
|
|
|
+ console.error('语音识别错误:', event.error);
|
|
|
+ clearInterval(countdownTimer.value); // 出错时清除定时器
|
|
|
+ isRecording.value = false;
|
|
|
+ Message().error('语音输入失败,请重试!', true)
|
|
|
+ countdown.value = 0;
|
|
|
+ };
|
|
|
+
|
|
|
+ return instance;
|
|
|
+};
|
|
|
+
|
|
|
+// 切换录音状态
|
|
|
+const toggleSpeechInput = () => {
|
|
|
+ // 清除可能存在的旧定时器
|
|
|
+ clearInterval(countdownTimer.value);
|
|
|
+ countdownTimer.value = null;
|
|
|
+
|
|
|
+ if (isRecording.value) {
|
|
|
+ // 手动停止时重置状态
|
|
|
+ countdown.value = 0;
|
|
|
+ recognition.value?.stop();
|
|
|
+ isRecording.value = false;
|
|
|
+ } else {
|
|
|
+ // 初始化倒计时前再次清除定时器(防止快速点击)
|
|
|
+ clearInterval(countdownTimer.value);
|
|
|
+ countdown.value = 10; // 重置为10秒
|
|
|
+
|
|
|
+ recognition.value = initSpeechRecognition();
|
|
|
+ if (!recognition.value) return;
|
|
|
+
|
|
|
+ navigator.mediaDevices.getUserMedia({ audio: true })
|
|
|
+ .then(() => {
|
|
|
+ recognition.value.start();
|
|
|
+ isRecording.value = true;
|
|
|
+
|
|
|
+ // 启动新的倒计时定时器
|
|
|
+ countdownTimer.value = setInterval(() => {
|
|
|
+ countdown.value--;
|
|
|
+ if (countdown.value <= 0) {
|
|
|
+ clearInterval(countdownTimer.value); // 倒计时结束清除
|
|
|
+ recognition.value.stop();
|
|
|
+ isRecording.value = false;
|
|
|
+ countdown.value = 0;
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.error("麦克风权限获取失败:", err);
|
|
|
+ alert("请允许麦克风权限以使用语音输入");
|
|
|
+ // 出错时重置状态
|
|
|
+ isRecording.value = false;
|
|
|
+ countdown.value = 0;
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 停止操作函数
|
|
|
+const stopStream = async () => {
|
|
|
+ // tip:如果 stream 进行中的 message,就需要调用 controller 结束
|
|
|
+ if (conversationInAbortController.value) {
|
|
|
+ conversationInAbortController.value.abort();
|
|
|
+ }
|
|
|
+ // 设置为 false
|
|
|
+ conversationInProgress.value = false;
|
|
|
+};
|
|
|
+
|
|
|
+// 发送消息函数,图片数据
|
|
|
+const sendMessage = async() => {
|
|
|
+ if (inputMessage.value.trim() || uploadedImage.value) {
|
|
|
+ // 创建 AbortController 实例,以便中止请求
|
|
|
+ conversationInAbortController.value = new AbortController();
|
|
|
+ // 标记对话进行中
|
|
|
+ conversationInProgress.value = true;
|
|
|
+ // 先保存内容 再置空输入框
|
|
|
+ let content = inputMessage.value;
|
|
|
+ inputMessage.value = '';
|
|
|
+
|
|
|
+
|
|
|
+ // 创建用户消息对象,包含可能的图片
|
|
|
+ const userMessage = {
|
|
|
+ type: 'user',
|
|
|
+ content: content,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 如果有上传的图片,添加到用户消息中
|
|
|
+ if (uploadedImage.value) {
|
|
|
+ userMessage.imageUrl = uploadedImage.value;
|
|
|
+ // 清空上传的图片
|
|
|
+ // uploadedImage.value = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ imageAllList.value.push(userMessage);
|
|
|
+ imageAllList.value.push({
|
|
|
+ type: 'ai',
|
|
|
+ content: "正在为您生成图片,请稍等...",
|
|
|
+ })
|
|
|
+
|
|
|
+ // 递增消息计数器
|
|
|
+ messageCount.value++
|
|
|
+ // 发送saveRecord请求 保存消息次数
|
|
|
+ try{
|
|
|
+ await saveRecord({
|
|
|
+ brpNjId: gradeId.value,
|
|
|
+ brpType: "aiCount",
|
|
|
+ brpProgress: messageCount.value
|
|
|
+ });
|
|
|
+ console.log('保存记录成功,消息次数:', messageCount.value);
|
|
|
+ }catch(error){
|
|
|
+ console.error('保存记录失败:', error);
|
|
|
+ conversationInProgress.value = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ CreatePainting({
|
|
|
+ "modelId": modelId.value,
|
|
|
+ "prompt":content,
|
|
|
+ "width":1024,
|
|
|
+ "height":1024,
|
|
|
+ "promptImage":uploadedImage.value
|
|
|
+ }).then(res=>{
|
|
|
+ console.log("生成图片",res)
|
|
|
+ //目前写死调用已生成的图片,全部通了后再改
|
|
|
+ inProgressImageMap.value[res.data] = {id:res.data,status:AiImageStatusEnum.IN_PROGRESS}
|
|
|
+ // inProgressImageMap.value[260] = {id:260,status:AiImageStatusEnum.IN_PROGRESS}
|
|
|
+ }).finally(() => {
|
|
|
+ // 图片生成请求完成后更新状态
|
|
|
+ conversationInProgress.value = false;
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('生成图片失败:', error);
|
|
|
+ conversationInProgress.value = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 调用子组件的方法清除预览图
|
|
|
+ imageUploadRef.value?.clearPreview();
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+// 生成图片
|
|
|
+import { ElIcon } from 'element-plus'
|
|
|
+import {
|
|
|
+ Back,
|
|
|
+ DArrowRight,
|
|
|
+ Download,
|
|
|
+ Refresh,
|
|
|
+ RefreshLeft,
|
|
|
+ RefreshRight,
|
|
|
+ Right,
|
|
|
+ ZoomIn,
|
|
|
+ ZoomOut,
|
|
|
+} from '@element-plus/icons-vue'
|
|
|
+
|
|
|
+
|
|
|
+const imageAllList = ref([]) // 对话的消息列表
|
|
|
+const imageList = ref([]) // image 列表
|
|
|
+// 图片轮询相关的参数(正在生成中的)
|
|
|
+const inProgressImageMap = ref({}) // 监听的 image 映射,一般是生成中(需要轮询),key 为 image 编号,value 为 image
|
|
|
+const inProgressTimer = ref() // 生成中的 image 定时器,轮询生成进展
|
|
|
+
|
|
|
+
|
|
|
+/** 轮询生成中的 image 列表 */
|
|
|
+const refreshWatchImages = async () => {
|
|
|
+ const imageIds = Object.keys(inProgressImageMap.value).map(Number)
|
|
|
+ if (imageIds.length === 0) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const list = await PaintingGetMys(imageIds)
|
|
|
+ const newWatchImages = {}
|
|
|
+ list.data.forEach((image) => {
|
|
|
+
|
|
|
+ if (image.status === AiImageStatusEnum.IN_PROGRESS) {
|
|
|
+ newWatchImages[image.id] = image
|
|
|
+ } else {
|
|
|
+ imageAllList.value.pop();
|
|
|
+ console.log('AI生成的图片地址:', image.picUrl);
|
|
|
+ imageAllList.value.push({
|
|
|
+ type: 'ai',
|
|
|
+ content: "已为您生成图片:",
|
|
|
+ imageList: [image.picUrl],
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ inProgressImageMap.value = newWatchImages
|
|
|
+ if (newWatchImages.size === 0) {
|
|
|
+ inProgressTimerFun()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/** 组件挂在的时候 */
|
|
|
+onMounted(async () => {
|
|
|
+ refreshWatchImagesFun()
|
|
|
+})
|
|
|
+
|
|
|
+/** 组件取消挂在的时候 */
|
|
|
+onUnmounted(async () => {
|
|
|
+ inProgressTimerFun()
|
|
|
+})
|
|
|
+
|
|
|
+// 自动刷新 image 列表
|
|
|
+const refreshWatchImagesFun = () => {
|
|
|
+ inProgressTimer.value = setInterval(async () => {
|
|
|
+ await refreshWatchImages()
|
|
|
+ }, 1000 * 3)
|
|
|
+}
|
|
|
+
|
|
|
+// 停止刷新image列表
|
|
|
+const inProgressTimerFun = () => {
|
|
|
+ if (inProgressTimer.value) {
|
|
|
+ clearInterval(inProgressTimer.value)
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+@use 'sass:math';
|
|
|
+// 定义rpx转换函数
|
|
|
+@function rpx($px) {
|
|
|
+ @return math.div($px, 750) * 100vw;
|
|
|
+}
|
|
|
+// 用户图片列表样式
|
|
|
+.user-image-list {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ margin-top: rpx(5);
|
|
|
+}
|
|
|
+/* 添加过渡样式 */
|
|
|
+.drawer-slide-enter-active,
|
|
|
+.drawer-slide-leave-active {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+.drawer-slide-enter-from,
|
|
|
+.drawer-slide-leave-to {
|
|
|
+ transform: translateX(-100%);
|
|
|
+ opacity: 0;
|
|
|
+}
|
|
|
+:deep(.el-image-viewer__wrapper) {
|
|
|
+ z-index: 10000 !important;
|
|
|
+}
|
|
|
+.icon-expand {
|
|
|
+ width: rpx(8);
|
|
|
+ height: rpx(35);
|
|
|
+ border-top-right-radius: rpx(5);
|
|
|
+ border-bottom-right-radius: rpx(5);
|
|
|
+ z-index: 9999;
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 18%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ background-color: #44449c;
|
|
|
+ cursor: pointer; // 添加鼠标指针样式
|
|
|
+ clip-path: polygon(0 0, 100% 15%, 100% 85%, 0 100%);
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+.icon-expand .vertical-lines {
|
|
|
+ color: #8a78d0;
|
|
|
+ font-size: rpx(10);
|
|
|
+}
|
|
|
+.menu-icon {
|
|
|
+ width:rpx(11);
|
|
|
+ height: rpx(11);
|
|
|
+ margin-right: rpx(2);
|
|
|
+}
|
|
|
+// 侧边栏
|
|
|
+.left-group1 {
|
|
|
+ width: rpx(135);
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(to bottom, #001169, #8a78d0);
|
|
|
+
|
|
|
+}
|
|
|
+.home-container {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ gap: rpx(0);
|
|
|
+ background: linear-gradient(
|
|
|
+ to bottom,
|
|
|
+ #e2ddfc,
|
|
|
+ #f1effd
|
|
|
+ ); /* 设置悬停、聚焦、点击状态下的背景色 */
|
|
|
+}
|
|
|
+
|
|
|
+// 侧边栏
|
|
|
+.left-group {
|
|
|
+ width: rpx(135);
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(to bottom, #001169, #8a78d0);
|
|
|
+}
|
|
|
+.mb-2 {
|
|
|
+ color: black;
|
|
|
+ margin-top: rpx(1);
|
|
|
+}
|
|
|
+.tac ::v-deep(.el-menu) {
|
|
|
+ background-color: transparent;
|
|
|
+ border: none;
|
|
|
+ width: 100%;
|
|
|
+ margin-top: rpx(55);
|
|
|
+ margin-left: rpx(10);
|
|
|
+}
|
|
|
+.el-menu-item {
|
|
|
+ width: rpx(115);
|
|
|
+ height: rpx(25);
|
|
|
+ margin-bottom: rpx(5);
|
|
|
+ border-radius: rpx(6);
|
|
|
+ color: white;
|
|
|
+ font-size: rpx(8);
|
|
|
+}
|
|
|
+.el-menu-item .el-icon svg {
|
|
|
+ font-size: rpx(15);
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.el-menu ::v-deep(.el-menu-item:hover),
|
|
|
+.el-menu ::v-deep(.el-menu-item:focus),
|
|
|
+.el-menu ::v-deep(.el-menu-item:active) {
|
|
|
+ background: linear-gradient(
|
|
|
+ to bottom,
|
|
|
+ #ffefb0,
|
|
|
+ #ffcc00
|
|
|
+ ); /* 设置悬停、聚焦、点击状态下的背景色 */
|
|
|
+ box-shadow: 0 8px 8px rgb(0, 0, 0, 0.3);
|
|
|
+ color: black;
|
|
|
+ font-size: rpx(8);
|
|
|
+}
|
|
|
+.el-menu-vertical-demo .el-menu-item.is-active {
|
|
|
+ /* 可根据需求修改选中样式 */
|
|
|
+ background: linear-gradient(
|
|
|
+ to bottom,
|
|
|
+ #ffefb0,
|
|
|
+ #ffcc00
|
|
|
+ ); /* 设置悬停、聚焦、点击状态下的背景色 */
|
|
|
+ box-shadow: 0 8px 8px rgb(0, 0, 0, 0.3);
|
|
|
+ color: black;
|
|
|
+ font-size: rpx(8);
|
|
|
+}
|
|
|
+
|
|
|
+// 侧边栏
|
|
|
+.left-group2 {
|
|
|
+ width: rpx(150);
|
|
|
+ height: 100%;
|
|
|
+ background-color: #ece9fd;
|
|
|
+}
|
|
|
+.left-group2 img {
|
|
|
+ width: rpx(110);
|
|
|
+ height: auto;
|
|
|
+ margin-top: rpx(30);
|
|
|
+}
|
|
|
+.title-box {
|
|
|
+ height: rpx(50);
|
|
|
+}
|
|
|
+.box-icon {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ flex: 1;
|
|
|
+ display: flex; // 添加 flex 布局
|
|
|
+ align-items: center; // 垂直居中
|
|
|
+ color: black; // 设置图标颜色为白色
|
|
|
+ padding-left: rpx(15);
|
|
|
+ font-size: rpx(10); // 设置图标大小,可按需调整
|
|
|
+ cursor: pointer; // 添加鼠标指针样式
|
|
|
+}
|
|
|
+.box-icon .left-icon {
|
|
|
+ margin-left: rpx(10);
|
|
|
+ margin-right: rpx(5); // 设置图标和文字之间的间距 ;
|
|
|
+}
|
|
|
+
|
|
|
+.number-people {
|
|
|
+ flex: 1;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ background-color: #ece9fd;
|
|
|
+}
|
|
|
+
|
|
|
+.content-box {
|
|
|
+ flex: 1;
|
|
|
+ margin-top: rpx(10);
|
|
|
+ margin-bottom: rpx(10);
|
|
|
+ margin-right: rpx(10);
|
|
|
+ border-radius: rpx(15);
|
|
|
+ background: rgba($color: #ffffff, $alpha: 0.5);
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+//左侧展览区图标
|
|
|
+.img-box {
|
|
|
+ margin-top: rpx(50);
|
|
|
+ color: #a39dce;
|
|
|
+}
|
|
|
+// 对话框
|
|
|
+.chat-dialog {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+.message-list {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: rpx(15);
|
|
|
+}
|
|
|
+/* 自定义滚动条样式 */
|
|
|
+.message-list::-webkit-scrollbar {
|
|
|
+ width: rpx(2); /* 滚动条宽度 */
|
|
|
+}
|
|
|
+.message-list::-webkit-scrollbar-track {
|
|
|
+ background: #f1effd; /* 滚动条轨道背景色 */
|
|
|
+ border-radius: rpx(4);
|
|
|
+}
|
|
|
+.message-list::-webkit-scrollbar-thumb {
|
|
|
+ background: #e2ddfc; /* 滚动条滑块颜色 */
|
|
|
+ border-radius: rpx(4);
|
|
|
+}
|
|
|
+.message-list::-webkit-scrollbar-thumb:hover {
|
|
|
+ background: #e2ddfc; /* 滚动条滑块 hover 状态颜色 */
|
|
|
+}
|
|
|
+
|
|
|
+.message-list .user-message {
|
|
|
+ background-color: #ffffff;
|
|
|
+ margin-left: auto; // 消息靠右显示
|
|
|
+ margin-right: 0; // 重置右边距
|
|
|
+ max-width: rpx(400);
|
|
|
+ font-size: rpx(8);
|
|
|
+ width: fit-content; // 宽度随文字内容变化
|
|
|
+ border-radius: rpx(5);
|
|
|
+ padding: rpx(5);
|
|
|
+ text-align: left; // 文字左对齐
|
|
|
+}
|
|
|
+
|
|
|
+.message-list .ai-message {
|
|
|
+ background-color: #ffdd55;
|
|
|
+ margin-left: 0; // 消息靠左显示
|
|
|
+ margin-right: auto; // 重置右边距
|
|
|
+ margin-bottom: rpx(10);
|
|
|
+ width: fit-content;
|
|
|
+ max-width: rpx(400);
|
|
|
+ padding: rpx(5);
|
|
|
+ font-size: rpx(8);
|
|
|
+ border-radius: rpx(5);
|
|
|
+ text-align: left; // 文字左对齐
|
|
|
+}
|
|
|
+.image-list {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+.content-demo {
|
|
|
+ background-color: #f4f2fa;
|
|
|
+ border-radius: 15px;
|
|
|
+ padding: 30px 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.input-section {
|
|
|
+ display: flex;
|
|
|
+ padding: rpx(10);
|
|
|
+ gap: rpx(5);
|
|
|
+ .speech-btn {
|
|
|
+ padding: rpx(5) rpx(10);
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #ffce1b;
|
|
|
+ border-radius: rpx(5);
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ &.recording {
|
|
|
+ background: #ffeeba;
|
|
|
+ border-color: #ffc107;
|
|
|
+
|
|
|
+ .el-icon {
|
|
|
+ color: #dc3545;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .el-icon {
|
|
|
+ font-size: rpx(8);
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 终止按钮样式
|
|
|
+ .stop-btn {
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ img {
|
|
|
+ width: rpx(20);
|
|
|
+ height: rpx(20);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.input-section input {
|
|
|
+ flex: 1;
|
|
|
+ padding: rpx(5);
|
|
|
+ font-size: rpx(7);
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ border-radius: rpx(5);
|
|
|
+}
|
|
|
+.input-section button {
|
|
|
+ padding: rpx(5) rpx(15);
|
|
|
+ background: linear-gradient(
|
|
|
+ to bottom,
|
|
|
+ #fee78a,
|
|
|
+ #ffce1b
|
|
|
+ ); /* 设置悬停、聚焦、点击状态下的背景色 */
|
|
|
+ color: black;
|
|
|
+ border: none;
|
|
|
+ font-size: rpx(7);
|
|
|
+ border-radius: rpx(5);
|
|
|
+ cursor: pointer;
|
|
|
+ box-shadow: 0 0px 2px rgba(0, 0, 0, 0.3);
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+.image-upload-section {
|
|
|
+ padding: rpx(10);
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+</style>
|