소스 검색

feat:添加AI Robot

AmandaG 7 달 전
부모
커밋
e933377594

+ 2 - 1
src/components/AIRobot/src/AIRobot.vue

@@ -72,12 +72,14 @@ const AvatarMouseLeave = () => {
     AIRobotHoverVisible.value = false
   }
 }
+const emit = defineEmits(['AvatarClick'])
 // 点击AIRobot图标
 const AvatarClick = () => {
   clicked.value = true
   AIRobotHoverVisible.value = false
   isShowDefault.value = false
   isShowAIRobotTop.value = false
+  emit('AvatarClick')
 }
 // 隐藏上方弹窗
 const HideAIRobotTop = () => {
@@ -318,7 +320,6 @@ defineExpose({
   width: 404px;
   height: 32px;
   padding: 5.5px 8px;
-  height: 32px;
   border-radius: 6px;
   background-color: var(--color-dialogue-bg);
   margin-bottom: 4px;

+ 3 - 2
src/views/AIRobotChat/src/AIRobotChat.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import AutoResizeTextarea from './components/AutoResizeTextarea.vue'
+import AIQuestions from './components/AIQuestions.vue'
 import userBubbleLight from './image/userBubbleLight.png'
 import userBubbleDark from './image/userBubbleDark.png'
 import robotBubbleLight from './image/robotBubbleLight.png'
@@ -144,8 +145,9 @@ const handleClose = () => {
           <span @click="handleClose" class="font_family icon-icon_collapsed__to_widget_b"></span>
         </div>
       </div>
+      <AIQuestions :modalSize="modalSize"></AIQuestions>
     </div>
-    <div class="chat-messages" ref="messagesRef">
+    <div class="chat-messages" ref="messagesRef" :style="{ marginTop: modalSize === 'large' ? '81px' : '151px' }">
       <div
         class="message-item"
         :class="[
@@ -284,7 +286,6 @@ const handleClose = () => {
     display: flex;
     flex-direction: column;
     gap: 16px;
-    margin-top: 81px;
     padding: 0 16px;
     overflow: auto;
     .message-item {

+ 263 - 0
src/views/AIRobotChat/src/components/AIQuestions.vue

@@ -0,0 +1,263 @@
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+
+const props = defineProps<{
+  modalSize: String
+}>()
+
+const DeQuestions = ref([
+  {
+    label: 'List shipments with ETA changes in the last 30 days.',
+    value: 'List shipments with ETA changes in the last 30 days.',
+    isLong: false
+  },
+  {
+    label: 'Shipments arriving in the next 7 days.',
+    value: 'Shipments arriving in the next 7 days.',
+    isLong: false
+  },
+  {
+    label: 'Show shipments delayed in the last 30 days.',
+    value: 'Show shipments delayed in the last 30 days.',
+    isLong: false
+  },
+  {
+    label: 'List shipments with milestone updates in the last 7 days.',
+    value: 'List shipments with milestone updates in the last 7 days.',
+    isLong: false
+  },
+  {
+    label: 'List shipments with milestone in the last 7 days2sdsa.',
+    value: 'List shipments with milestone in the last 7 days2sdsa.',
+    isLong: false
+  },
+  {
+    label: 'List shipments with milestone updates 7 days3fef.',
+    value: 'List shipments with milestone updates 7 days3fef.',
+    isLong: false
+  },
+  {
+    label: 'List shipments with ETA changes in the last 30 days11111111111111111111111111111111111.',
+    value: 'List shipments with ETA changes in the last 30 days11111111111111111111111111111111111.',
+    isLong: true
+  },
+  {
+    label: 'List shipments with ETA changes in the last 30 days1111111111111111111111111111111111122.',
+    value: 'List shipments with ETA changes in the last 30 days1111111111111111111111111111111111122.',
+    isLong: true
+  },
+  {
+    label: 'List shipments with ETA changes in the last 30 days1111111111111111111111111111111111133.',
+    value: 'List shipments with ETA changes in the last 30 days1111111111111111111111111111111111133.',
+    isLong: true
+  },
+])
+const itemGroups = ref([]);
+const pages = ref([]);
+// 随机显示方法
+const prepareGroups = () => {
+  const groups = [];
+  let currentGroup = [];
+  let currentHeight = 0;
+
+  DeQuestions.value.forEach(item => {
+    const itemHeight = item.isLong ? 2 : 1;
+    
+    if (currentHeight + itemHeight > 4) {
+      groups.push(currentGroup);
+      currentGroup = [];
+      currentHeight = 0;
+    }
+    
+    currentGroup.push(item);
+    currentHeight += itemHeight;
+  });
+
+  // 添加最后一组
+  if (currentGroup.length > 0) {
+    groups.push(currentGroup);
+  }
+
+  itemGroups.value = groups;
+}
+// 智能分页算法
+const generatePages = () => {
+  const result = [];
+  let currentPage = { row1: [], row2: [] };
+  let cursor = 0;
+  
+  while(cursor < DeQuestions.value.length) {
+    // 处理第一排
+    const currentItem = DeQuestions.value[cursor];
+    // 第一排逻辑
+    if(currentItem.isLong) {
+      // 长文本独占第一排
+      currentPage.row1.push(currentItem);
+      cursor++;
+    } else {
+      // 尝试取两个短文本
+      const nextItem = DeQuestions.value[cursor + 1];
+      if(nextItem && !nextItem.isLong) {
+        currentPage.row1.push(currentItem, nextItem);
+        cursor += 2;
+      } else {
+        currentPage.row1.push(currentItem);
+        cursor++;
+      }
+    }
+
+    // 处理第二排
+    const remaining = DeQuestions.value.slice(cursor);
+    if(remaining.length > 0) {
+      const secondItem = remaining[0];
+      
+      if(secondItem.isLong) {
+        currentPage.row2.push(secondItem);
+        cursor++;
+      } else {
+        const nextSecondItem = remaining[1];
+        if(nextSecondItem && !nextSecondItem.isLong) {
+          currentPage.row2.push(secondItem, nextSecondItem);
+          cursor += 2;
+        } else {
+          currentPage.row2.push(secondItem);
+          cursor++;
+        }
+      }
+    }
+
+    // 保存当前页
+    result.push(currentPage);
+    
+    // 重置页面
+    currentPage = { row1: [], row2: [] };
+  }
+  pages.value = result.filter(p => p.row1.length > 0);
+  console.log(pages.value)
+}
+onMounted(() => {
+  prepareGroups()
+  generatePages()
+});
+</script>
+<template>
+  <div class="flex_center">
+    <div class="dialogue_content ">
+      <div class="dialogue_content_title">
+        <div class="dialogue_title_left">
+          <img class="dialogue_title_left_img" src="../image/icon_ai_robot48_b@2x.png" width="48px" />
+          <div class="dialogue_title_left_text">Frequently Asked Questions</div>
+        </div>
+      </div>
+      <el-carousel v-if="props.modalSize === 'large'"  class="carousel large_carousel" :autoplay="false" height="115px" >
+        <el-carousel-item 
+          v-for="(page, index) in pages "
+          :key="index"
+        >
+          <div class="dialogue_container dialogue_container_large">
+            <div class="double-row-layout">
+              <!-- 第一排 -->
+              <div class="row first-row">
+                <template v-for="item in page.row1" :key="item.id">
+                  <div class="dialogue_content_item" :class="{ 'long-item': item.isLong }">
+                    {{ item.label }}
+                  </div>
+                </template>
+              </div>
+              <!-- 第二排 -->
+              <div class="row second-row">
+                <template v-for="item in page.row2" :key="item.id">
+                  <div class="dialogue_content_item" :class="{ 'long-item': item.isLong }">
+                    {{ item.label }}
+                  </div>
+                </template>
+              </div>
+            </div>
+          </div>
+        </el-carousel-item>
+      </el-carousel>
+      <el-carousel v-else  class="carousel small_carousel" :autoplay="false" height="190px" >
+        <el-carousel-item 
+          v-for="(group, index) in itemGroups "
+          :key="index"
+        >
+          <div class="dialogue_container">
+            <div 
+              class="dialogue_content_item"
+              v-for="item in group"
+              :key="item.label"
+              :class="{ 'long_item': item.isLong }"
+            >
+              {{ item.label }}
+            </div>
+          </div>
+        </el-carousel-item>
+      </el-carousel>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.dialogue_content {
+  background: linear-gradient(117deg, var(--1-gradient-ai-robot-0, #B5FEF3) 4.31%, var(--1-gradient-ai-robot-15, #F9EEEC) 14.24%, var(--1-gradient-ai-robot-38, #FBD3EE) 29.71%, var(--1-gradient-ai-robot-59, #DDD5F7) 43.72%, var(--1-gradient-ai-robot-83, #C8F4F3) 59.35%, var(--1-gradient-ai-robot-100, #CADFF8) 70.56%);
+}
+.dialogue_title_left {
+  position: relative;
+}
+.dialogue_title_left_img {
+  position: absolute;
+  top: -22px;
+}
+.dialogue_title_left_text {
+  background: linear-gradient(90deg, #A71549 1.77%, #06256E 46.77%);
+  -webkit-background-clip: text;
+  background-clip: text;
+  color: transparent;
+  font-size: 14px;
+  font-weight: 700;
+  margin: 5px 0 0 55px;
+}
+.small_carousel {
+  width: 452px;
+}
+.large_carousel {
+  width: 968px;
+}
+.double-row-layout {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+.row {
+  flex: 1;
+  display: flex;
+  min-height: 0; /* 防止内容溢出 */
+}
+.long-item {
+  grid-column: span 2;
+}
+.dialogue_container_large {
+  width: 936px;
+  height: 84px;
+}
+.dialogue_content_item {
+  width: 100%;
+  height: 32px;
+  padding: 5.5px 8px;
+  border-radius: 6px;
+  background-color: var(--color-dialogue-bg);
+  margin-right: 4px;
+  word-break: break-word;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: start;
+  text-align: center;
+  min-width: 0; /* 防止文本溢出 */
+}
+:deep(.el-carousel__item--card, .el-carousel__item.is-animating) {
+  padding-bottom: 0 !important;
+}
+</style>
+

BIN
src/views/AIRobotChat/src/image/icon_ai_robot36_b@2x.png


BIN
src/views/AIRobotChat/src/image/icon_ai_robot48_b@2x.png


BIN
src/views/AIRobotChat/src/image/icon_faq_b@2x.png


+ 5 - 2
src/views/Layout/src/LayoutView.vue

@@ -22,6 +22,10 @@ const handleColseRobotChat = () => {
 const onClick = () => {
   isShowAIRobotChat.value = true
 }
+
+const AvatarClick = () => {
+  isShowAIRobotChat.value = true
+}
 </script>
 <template>
   <el-container class="layout-container">
@@ -52,8 +56,7 @@ const onClick = () => {
         <router-view />
       </el-main>
     </el-container>
-    <AIRobot></AIRobot>
-    <!-- <AIRobot></AIRobot> -->
+    <AIRobot @AvatarClick="AvatarClick"></AIRobot>
     <AIRobotChat v-if="isShowAIRobotChat" @close="handleColseRobotChat"></AIRobotChat>
     <ScoringGrade></ScoringGrade>
   </el-container>