Преглед изворни кода

Merge branch 'dev_zyh' of United_Software/k_online_ui into dev

Jack Zhou пре 7 месеци
родитељ
комит
00c9fe3cb4
26 измењених фајлова са 610 додато и 270 уклоњено
  1. 2 0
      package.json
  2. 61 58
      src/components/AIRobot/src/AIRobot.vue
  3. 0 1
      src/components/AddRules/src/components/NotiMethods.vue
  4. 0 1
      src/components/CreateAddRules/src/CreateAddRules.vue
  5. 0 1
      src/components/CreateAddRules/src/components/ShipmentRange.vue
  6. 1 2
      src/components/NotificationMessageCard/src/NotificationMessageCard.vue
  7. 0 1
      src/components/NotificationMessageCard/src/components/EventCard.vue
  8. 4 2
      src/components/SelectTableSelect/src/SelectTableSelect.vue
  9. 0 0
      src/styles/icons/iconfont.js
  10. BIN
      src/styles/icons/iconfont.ttf
  11. BIN
      src/styles/icons/iconfont.woff
  12. BIN
      src/styles/icons/iconfont.woff2
  13. 65 7
      src/styles/reset.scss
  14. 18 5
      src/styles/theme.scss
  15. 292 100
      src/views/AIRobotChat/src/AIRobotChat.vue
  16. 96 75
      src/views/AIRobotChat/src/components/AIQuestions.vue
  17. 46 0
      src/views/AIRobotChat/src/components/LoadingDots.vue
  18. 0 1
      src/views/Layout/src/LayoutView.vue
  19. 0 3
      src/views/Layout/src/components/Header/components/NotificationDrawer.vue
  20. 6 0
      src/views/Layout/src/components/Header/components/TrainingCard.vue
  21. 2 2
      src/views/Login/src/components/ChangePasswordCard.vue
  22. 3 3
      src/views/Login/src/loginView.vue
  23. 7 2
      src/views/SystemMessage/src/SystemMessage.vue
  24. 1 0
      src/views/Tracking/src/TrackingView.vue
  25. 5 5
      src/views/Tracking/src/components/TrackingDetail/src/TrackingDetail.vue
  26. 1 1
      vite.config.ts

+ 2 - 0
package.json

@@ -30,8 +30,10 @@
     "echarts": "^5.5.1",
     "element-plus": "^2.8.1",
     "exceljs": "^4.4.0",
+    "github-markdown-css": "^5.8.1",
     "leaflet": "^1.9.4",
     "lodash": "^4.17.21",
+    "markdown-it": "^14.1.0",
     "mitt": "^3.0.1",
     "moment": "^2.30.1",
     "moment-timezone": "^0.5.46",

+ 61 - 58
src/components/AIRobot/src/AIRobot.vue

@@ -41,22 +41,28 @@ const DeQuestions = ref([
     isLong: false
   },
   {
-    label: 'List shipments with ETA changes in the last 30 days11111111111111111111111111111111111.',
-    value: 'List shipments with ETA changes in the last 30 days11111111111111111111111111111111111.',
+    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.',
+    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.',
+    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 itemGroups = ref([])
 
 // 鼠标hover AIRobot图标
 const AvatarMouseEnter = () => {
@@ -93,29 +99,29 @@ const HideAIRobotTopTwo = () => {
 
 // 随机显示方法
 const prepareGroups = () => {
-  const groups = [];
-  let currentGroup = [];
-  let currentHeight = 0;
+  const groups = []
+  let currentGroup = []
+  let currentHeight = 0
+
+  DeQuestions.value.forEach((item) => {
+    const itemHeight = item.isLong ? 2 : 1
 
-  DeQuestions.value.forEach(item => {
-    const itemHeight = item.isLong ? 2 : 1;
-    
     if (currentHeight + itemHeight > 4) {
-      groups.push(currentGroup);
-      currentGroup = [];
-      currentHeight = 0;
+      groups.push(currentGroup)
+      currentGroup = []
+      currentHeight = 0
     }
-    
-    currentGroup.push(item);
-    currentHeight += itemHeight;
-  });
+
+    currentGroup.push(item)
+    currentHeight += itemHeight
+  })
 
   // 添加最后一组
   if (currentGroup.length > 0) {
-    groups.push(currentGroup);
+    groups.push(currentGroup)
   }
 
-  itemGroups.value = groups;
+  itemGroups.value = groups
 }
 
 const isShowLogin = () => {
@@ -123,17 +129,17 @@ const isShowLogin = () => {
   setTimeout(() => {
     isShowDefault.value = false
     isShowAIRobotTop.value = true
-  }, 5000);
+  }, 5000)
 }
 
 onMounted(() => {
   prepareGroups()
-  emitter.on('login-success', isShowLogin);
-});
+  emitter.on('login-success', isShowLogin)
+})
 
 onUnmounted(() => {
-  emitter.off('login-success', isShowLogin);
-});
+  emitter.off('login-success', isShowLogin)
+})
 
 defineExpose({
   isShowLogin
@@ -145,39 +151,34 @@ defineExpose({
     <div class="flex_end" @click="HideAIRobotTop">
       <div class="icon flex_center">
         <span class="iconfont_icon icon_dark">
-            <svg class="iconfont" aria-hidden="true">
-              <use xlink:href="#icon-icon_reject_b"></use>
-            </svg>
-          </span>
+          <svg class="iconfont" aria-hidden="true">
+            <use xlink:href="#icon-icon_reject_b"></use>
+          </svg>
+        </span>
       </div>
     </div>
     <div class="flex_title">
       <div class="AIAvator">
         <img width="40px" src="../image/icon_ai_robot36_b@2x.png" />
       </div>
-      <div class="dialogue_title">
-        Hi! I'm your Freight Assistant, always on call
-      </div>
+      <div class="dialogue_title">Hi! I'm your Freight Assistant, always on call</div>
     </div>
     <div class="flex_end">
-      <div class="dialogue_content ">
+      <div class="dialogue_content">
         <div class="dialogue_content_title">
           <div class="dialogue_title_left">
             <img src="../image/icon_faq_b@2x.png" width="24px" />
             Frequently Asked Questions
           </div>
         </div>
-        <el-carousel class="carousel" :autoplay="false" height="190px" style="width: 452px;" >
-          <el-carousel-item 
-            v-for="(group, index) in itemGroups "
-            :key="index"
-          >
+        <el-carousel class="carousel" :autoplay="false" height="190px" style="width: 452px">
+          <el-carousel-item v-for="(group, index) in itemGroups" :key="index">
             <div class="dialogue_container">
-              <div 
+              <div
                 class="dialogue_content_item"
                 v-for="item in group"
                 :key="item.label"
-                :class="{ 'long_item': item.isLong }"
+                :class="{ long_item: item.isLong }"
               >
                 {{ item.label }}
               </div>
@@ -191,25 +192,27 @@ defineExpose({
     <div class="flex_end" @click="HideAIRobotTopTwo">
       <div class="icon flex_center">
         <span class="iconfont_icon icon_dark">
-            <svg class="iconfont" aria-hidden="true">
-              <use xlink:href="#icon-icon_reject_b"></use>
-            </svg>
-          </span>
+          <svg class="iconfont" aria-hidden="true">
+            <use xlink:href="#icon-icon_reject_b"></use>
+          </svg>
+        </span>
       </div>
     </div>
-    <div class="dialogue_title">
-      Hi! I'm your Freight Assistant, always on call
-    </div>
+    <div class="dialogue_title">Hi! I'm your Freight Assistant, always on call</div>
   </div>
   <!-- 悬浮icon -->
   <div class="AIRobot flex_center">
-    <el-popover
-      :visible="visible"
-      placement="top-end"
-      width="auto"
-    >
+    <el-popover :visible="visible" placement="top-end" width="auto">
       <template #reference>
-        <el-avatar @click="AvatarClick" @mouseenter="AvatarMouseEnter" @mouseleave="AvatarMouseLeave" :size="46" shape="square" class="avatar_bg" :src="clickSrc" />
+        <el-avatar
+          @click="AvatarClick"
+          @mouseenter="AvatarMouseEnter"
+          @mouseleave="AvatarMouseLeave"
+          :size="46"
+          shape="square"
+          class="avatar_bg"
+          :src="clickSrc"
+        />
       </template>
       <!-- hover时显示的对话框 -->
       <div v-if="AIRobotHoverVisible" class="AIRobot_dialog">Continue the conversation</div>
@@ -329,10 +332,10 @@ defineExpose({
   overflow: hidden;
   transition: all 0.3s ease;
 }
-.itemLable { 
+.itemLable {
   width: 100%;
 }
 .long_item {
   height: 64px;
 }
-</style>
+</style>

+ 0 - 1
src/components/AddRules/src/components/NotiMethods.vue

@@ -45,7 +45,6 @@ const MethodsInit = () => {
 
 // 选中Method
 const changeMethod = (val: any) => {
-  console.log(val)
   if (val.indexOf('By Email') != -1) {
     savesubscribeobj.method_by_email = true
   } else {

+ 0 - 1
src/components/CreateAddRules/src/CreateAddRules.vue

@@ -160,7 +160,6 @@ const Initdata = () => {
             ShipmentRangeCon.value = res.data.Container_Status_Update
             createObj.Transportstr = res.data.Container_Status_Update.shipment_details.split(';\r\n')[0]
             createObj.Timestr = res.data.Container_Status_Update.shipment_details.split(';\r\n')[1]
-            console.log(res.data.Container_Status_Update.shipment_details.split(';\r\n')[1])
             createListContainer.value.push(createObj.Transportstr)
             createListContainer.value.push(createObj.Timestr)
           } else if (editTablerules_type == 'Departure/Arrival_Delay') {

+ 0 - 1
src/components/CreateAddRules/src/components/ShipmentRange.vue

@@ -40,7 +40,6 @@ watch(
 )
 
 const ShipmentRangeInit = () => {
-  console.log(ShipmentRange_data.value)
   if (ShipmentRange_data.value?.shipment_transport_mode != undefined) {
     TransportCheckedList.value = ShipmentRange_data.value?.shipment_transport_mode.split(';')
     CheckChange(ShipmentRange_data.value?.shipment_transport_mode)

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

@@ -191,7 +191,6 @@ const parentHeight = computed(() => {
   return (window.innerHeight || document.documentElement.clientHeight) - props.topOffset
 })
 const scrollParentBoxStyle = computed(() => {
-  console.log(props.topOffset, 'value')
   return props.topOffset ? { height: `${parentHeight.value}px` } : {}
 })
 </script>
@@ -240,7 +239,7 @@ const scrollParentBoxStyle = computed(() => {
   width: 100%;
   overflow-y: auto;
   .scroll-padding {
-    padding: 10px 140px 0px 16px;
+    padding: 0px 140px 0px 16px;
   }
 }
 </style>

+ 0 - 1
src/components/NotificationMessageCard/src/components/EventCard.vue

@@ -185,7 +185,6 @@ const jumpTracking = (data: EventCardPropsData) => {
 
 <style lang="scss" scoped>
 .notification-card {
-  // max-width: 640px;
   margin-bottom: 16px;
   &.is-read {
     & > .header {

+ 4 - 2
src/components/SelectTableSelect/src/SelectTableSelect.vue

@@ -75,14 +75,16 @@ const changeSelect = (val: any) => {
 const emit = defineEmits(['changeAutoSelectAddType', 'delSelect', 'changeAutoSelect'])
 const errorBoolean = ref(false)
 let AutoSelectObj: any = {}
+let AutoSelectObj2: any = {}
 const changeAutoSelect = (val: any, value: any) => {
   if (value.length) {
     errorBoolean.value = true
   } else {
     errorBoolean.value = false
   }
-  AutoSelectObj[val] = value
-  emit('changeAutoSelect', AutoSelectObj, errorBoolean.value)
+  AutoSelectObj[val] = value.join()
+  AutoSelectObj2[val] = value
+  emit('changeAutoSelect', AutoSelectObj, AutoSelectObj2,errorBoolean.value)
 }
 const typeSelectFocus = (index: any, e: any) => {
   typeSelectIndex.value = index

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
src/styles/icons/iconfont.js


BIN
src/styles/icons/iconfont.ttf


BIN
src/styles/icons/iconfont.woff


BIN
src/styles/icons/iconfont.woff2


+ 65 - 7
src/styles/reset.scss

@@ -8,12 +8,6 @@ span,
 applet,
 object,
 iframe,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
 p,
 blockquote,
 pre,
@@ -26,7 +20,6 @@ cite,
 code,
 del,
 dfn,
-em,
 img,
 ins,
 kbd,
@@ -141,3 +134,68 @@ div {
     margin: 0;
   }
 }
+
+.markdown-body {
+  line-height: 1.6;
+  font-size: 16px;
+  color: #333;
+}
+
+.markdown-body h1,
+.markdown-body h2,
+.markdown-body h3,
+.markdown-body h4,
+.markdown-body h5,
+.markdown-body h6 {
+  margin: 1em 0 0.5em;
+  font-weight: bold;
+}
+
+.markdown-body p {
+  margin: 1em 0;
+}
+
+.markdown-body ul,
+.markdown-body ol {
+  margin-left: 1.5em;
+  padding-left: 1em;
+}
+
+.markdown-body ul {
+  list-style-type: disc;
+}
+
+.markdown-body ol {
+  list-style-type: decimal;
+}
+
+.markdown-body li {
+  margin: 0.5em 0;
+}
+
+.markdown-body blockquote {
+  padding-left: 1em;
+  border-left: 4px solid #ddd;
+  color: #555;
+  margin: 1em 0;
+}
+
+.markdown-body a {
+  color: #0366d6;
+  text-decoration: underline;
+}
+
+.markdown-body code {
+  background: #f5f5f5;
+  padding: 0.2em 0.4em;
+  border-radius: 4px;
+  font-family: monospace;
+  font-size: 0.9em;
+}
+
+.markdown-body pre code {
+  display: block;
+  padding: 1em;
+  background: #f6f8fa;
+  overflow-x: auto;
+}

+ 18 - 5
src/styles/theme.scss

@@ -285,17 +285,27 @@
   --color-dialogue-bg: #fff;
   --color-dialogue_container-border: #fff;
   --color-carousel-card-bg: #fff;
-  --color-dialogue-icon-bg: radial-gradient(50% 43% at 50% 54.79%, #D5B4F3 0%, #FFF9FC 100%);
-  --color-dialogue-text-bg: linear-gradient(92deg, #EAECFF 1.33%, #F1E3FB 99.63%);
-  --color-dialogue_title: linear-gradient(90deg, #A71549 1.77%, #06256E 46.77%);
-  --color-dialogue_container-bg:rgba(255, 255, 255, 0.50);
-  --color-dialogue_content-bg: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%);
+  --color-dialogue-icon-bg: radial-gradient(50% 43% at 50% 54.79%, #d5b4f3 0%, #fff9fc 100%);
+  --color-dialogue-text-bg: linear-gradient(92deg, #eaecff 1.33%, #f1e3fb 99.63%);
+  --color-dialogue_title: linear-gradient(90deg, #a71549 1.77%, #06256e 46.77%);
+  --color-dialogue_container-bg: rgba(255, 255, 255, 0.5);
+  --color-dialogue_content-bg: 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%
+  );
   --color-ai-chat-header-bg-gradient-begin: #eaecff;
   --color-ai-chat-header-bg-gradient-end: #fefdff;
   --color-ai-user-bubble-bg-gradient-begin: #ffede6;
   --color-ai-user-bubble-bg-gradient-end: #f2f4f7;
   --input-border: #eaebed;
   --color-pause-btn-bg: #fff1e6;
+  --color-loading-text: #b5b9bf;
+  --color-warning-tips-bg: #fff4d1;
 }
 
 :root.dark {
@@ -477,4 +487,7 @@
   --color-ai-user-bubble-bg-gradient-end: #5c6a7d;
   --input-border: #656f7d;
   --color-pause-btn-bg: #453b36;
+  --color-loading-text: #818892;
+  --color-warning-tips-bg: #85681b;
+  --color-warning-tips-text: #edb82f;
 }

+ 292 - 100
src/views/AIRobotChat/src/AIRobotChat.vue

@@ -1,11 +1,25 @@
 <script setup lang="ts">
 import AutoResizeTextarea from './components/AutoResizeTextarea.vue'
+import LoadingDots from './components/LoadingDots.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'
 import robotBubbleDark from './image/robotBubbleDark.png'
 import { useThemeStore } from '@/stores/modules/theme'
+import MarkdownIt from 'markdown-it'
+import 'github-markdown-css/github-markdown.css'
+
+const md = new MarkdownIt({
+  html: true,
+  linkify: true,
+  typographer: true,
+  breaks: true
+})
+
+const renderedMessage = (content) => {
+  return md.render(content)
+}
 
 const themeStore = useThemeStore()
 const modalSize = ref('large')
@@ -20,16 +34,25 @@ const robotBubbleImg = computed(() => {
   return themeStore.theme === 'light' ? robotBubbleLight : robotBubbleDark
 })
 
+// 是否显示footer的顶部阴影
+const isShowFooterShadow = ref(false)
+// 是否显示header的底部阴影
+const isShowHeaderShadow = ref(false)
+
+const loadingAnswer = ref(false) // 是否正在加载答案
 interface MessageItem {
   type: 'robot' | 'user'
   content: string
   feedback?: 'good' | 'noGood' | '' // 反馈结果
   isShowFeedback?: boolean // 是否展示反馈样式
   isAnswer?: boolean // 是否为用户问题的答案,是则才能展示反馈组件
+  isError?: boolean // 是否为错误消息
+  isCancel?: boolean // 是否为取消消息
 }
 const messages = ref<MessageItem[]>([
   {
     type: 'robot',
+    isShowFeedback: false,
     content: 'You can click on Frequently Asked Questions above or type your own question'
   },
   {
@@ -48,6 +71,19 @@ const messages = ref<MessageItem[]>([
     content: 'Of course! Please provide me with the details of your shipment.'
   }
 ])
+messages.value = JSON.parse(sessionStorage.getItem('AIChat')) || messages.value
+onMounted(() => {
+  scrollToBottom()
+})
+
+watch(
+  () => messages.value,
+  (newVal) => {
+    // 将消息存储到 sessionStorage 中
+    sessionStorage.setItem('AIChat', JSON.stringify(newVal))
+  },
+  { immediate: true, deep: true }
+)
 
 // 用户问题请求时间
 const queryTime = ref(-1)
@@ -62,59 +98,128 @@ const progressStatus = {
   cancel: 'You have stopped this answer'
 }
 
+const isShowTips = ref(false) // 是否展示提示信息
+
 const progressInterval = ref()
-const handleSend = () => {
-  if (!userQuestion.value) return
+const handleSend = (question, isPresetQuestion = true) => {
+  if (!question) return
+
+  if (loadingAnswer.value) {
+    isShowTips.value = true
+    setTimeout(() => {
+      isShowTips.value = false
+    }, 2000)
+    return
+  }
+  loadingAnswer.value = true
 
   messages.value.push({
     type: 'user',
-    content: userQuestion.value
+    content: question
   })
-  userQuestion.value = ''
+  !isPresetQuestion ? (userQuestion.value = '') : ''
   queryTime.value = 0
   messages.value.push({
     type: 'robot',
     content: progressStatus[0]
   })
+  autoScroll.value = true
+  scrollToBottom()
+
   progressInterval.value = setInterval(() => {
     queryTime.value++
+    // 定义时间点与对应状态的映射
+    const timeToStatusMap = {
+      15: 15,
+      30: 30,
+      60: 60
+    }
+    // 获取最后一个消息对象
+    const lastMessage = messages.value[messages.value.length - 1]
+    if (queryTime.value === 120) {
+      clearInterval(progressInterval.value)
+      lastMessage.content = progressStatus[120]
+      lastMessage.isError = true
+      queryTime.value = -1
+      loadingAnswer.value = false
+      return
+    }
+    if (timeToStatusMap[queryTime.value] !== undefined) {
+      lastMessage.content = progressStatus[queryTime.value]
+    }
   }, 1000)
 }
+const showScrollButton = ref(false) // 控制按钮显示
+const messagesRef = ref()
+const autoScroll = ref(true)
 
-// 根据时间更新消息内容
-const updateMessageContent = (time) => {
-  const lastMessageIndex = messages.value.length - 1
+function handleScroll() {
+  const el = messagesRef.value
+  const threshold = 50 // 到底部的距离阈值
+  const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < threshold
+  autoScroll.value = atBottom
 
-  // 确保消息数组不为空
-  if (lastMessageIndex >= 0) {
-    messages.value[lastMessageIndex].content = progressStatus[time]
-    if (time === 120) {
-      clearInterval(progressInterval.value)
-      queryTime.value = -3
-    }
-  }
+  showScrollButton.value = el.scrollHeight > el.clientHeight && !autoScroll.value
+
+  isShowFooterShadow.value =
+    messagesRef.value?.scrollTop <
+    messagesRef.value?.scrollHeight - messagesRef.value?.clientHeight - 1
+
+  isShowHeaderShadow.value = !!messagesRef.value?.scrollTop
 }
-watch(
-  () => queryTime.value,
-  (newVal) => {
-    // 定义时间点与对应状态的映射
-    const timeToStatusMap = {
-      15: 15,
-      30: 30,
-      60: 60,
-      120: 120
+function scrollToBottom() {
+  nextTick(() => {
+    if (messagesRef.value) {
+      messagesRef.value.scrollTop = messagesRef.value.scrollHeight
     }
+  })
+}
+
+const simulateStreamingMarkdown = () => {
+  loadingAnswer.value = true
+  const chunks = [
+    '# 欢迎使用 Markdown\n\n',
+    '这是一个用于测试的 **Markdown** 文本。\n\n',
+    '你可以使用 *斜体* 来强调重点。\n\n',
+    '也可以使用 [链接](https://example.com) 引导用户跳转。\n\n',
+    '- 支持无序列表\n',
+    '- 非常适合逐行流式显示\n\n',
+    '这是一段测试!\n'
+  ]
+
+  messages.value.push({
+    type: 'robot',
+    content: '',
+    isAnswer: true,
+    feedback: '',
+    isShowFeedback: false
+  })
+
+  let index = 0
 
-    // 如果当前时间点在映射中,更新消息内容
-    if (timeToStatusMap[newVal] !== undefined) {
-      updateMessageContent(timeToStatusMap[newVal])
+  autoScroll.value = true
+  scrollToBottom()
+
+  const interval = setInterval(() => {
+    index === chunks.length - 1 ? (loadingAnswer.value = false) : ''
+    if (index < chunks.length) {
+      messages.value[messages.value.length - 1].content += chunks[index]
+      index++
+
+      if (autoScroll.value) {
+        scrollToBottom()
+      }
+    } else {
+      clearInterval(interval)
     }
-  }
-)
+  }, 200) // 每500ms追加一段
+}
+
 // 暂停回答
 const handlePause = () => {
   clearInterval(progressInterval.value)
-  queryTime.value = -2
+  queryTime.value = -1
+  messages.value[messages.value.length - 1].isCancel = true
   messages.value[messages.value.length - 1].content = progressStatus.cancel
 }
 
@@ -128,7 +233,7 @@ const handleClose = () => {
 
 <template>
   <div class="ai-robot" :style="{ width: modalSize === 'large' ? '1000px' : '484px' }">
-    <div class="top-section">
+    <div class="top-section" :class="[isShowHeaderShadow ? 'box-shadow' : '']">
       <div class="header">
         <span class="welcome">Hi! I'm your Freight Assistant</span>
         <div class="option-icon">
@@ -145,14 +250,20 @@ const handleClose = () => {
           <span @click="handleClose" class="font_family icon-icon_collapsed__to_widget_b"></span>
         </div>
       </div>
-      <AIQuestions :modalSize="modalSize"></AIQuestions>
+      <AIQuestions :modalSize="modalSize" @question="handleSend"></AIQuestions>
+      <div class="warning-tips" v-if="isShowTips">
+        <div class="warning-bg">
+          <span class="warning-icon font_family icon-icon_warning_fill_b"></span>
+        </div>
+        <span>Answer in progress, please wait.</span>
+      </div>
     </div>
-    <div class="chat-messages" ref="messagesRef" :style="{ marginTop: modalSize === 'large' ? '81px' : '151px' }">
+    <div class="chat-messages" ref="messagesRef" @scroll="handleScroll">
       <div
         class="message-item"
         :class="[
           msg.type === 'user' ? 'user-bubble' : 'robot-bubble',
-          ((queryTime > -1 && queryTime < 120) || queryTime === -2) && index === messages.length - 1
+          (queryTime > -1 && queryTime < 120 && index === messages.length - 1) || msg.isCancel
             ? 'query-style'
             : ''
         ]"
@@ -161,12 +272,10 @@ const handleClose = () => {
         @mouseenter="msg.isShowFeedback = true"
         @mouseleave="msg.isShowFeedback = false"
       >
-        <!-- 请求失败后的提示icon -->
-        <span
-          v-if="queryTime === -3 && index === messages.length - 1"
-          class="font_family icon-icon_warning_fill_b"
-          style="margin-top: 1px; color: #c9353f"
-        ></span>
+        <!-- 请求失败后的提示icon   -->
+        <div class="pause-bg" v-if="msg.isError">
+          <span class="pause-icon font_family icon-icon_warning_fill_b"></span>
+        </div>
         <!-- loading icon -->
         <img
           class="loading-img"
@@ -174,7 +283,15 @@ const handleClose = () => {
           src="./image/icon_loading.png"
           alt=""
         />
-        {{ msg.content }}
+        <span v-if="!msg.isAnswer">{{ msg.content }}</span>
+        <div v-else>
+          <div v-html="renderedMessage(msg.content)" class="markdown-body"></div>
+          <LoadingDots
+            v-if="index === messages.length - 1 && msg.isAnswer && loadingAnswer"
+          ></LoadingDots>
+          <div></div>
+        </div>
+        <!-- 评价  -->
         <div class="review" v-if="msg.isShowFeedback && msg.isAnswer">
           <el-button
             v-if="msg.feedback !== 'good'"
@@ -209,27 +326,38 @@ const handleClose = () => {
         <!-- 暂停回答 icon -->
         <div
           class="pause-btn"
-          v-if="index === messages.length - 1 && queryTime > 30 && queryTime < 120"
+          v-if="index === messages.length - 1 && queryTime > 29 && queryTime < 120"
           @click="handlePause"
         >
           <div class="dot"></div>
         </div>
       </div>
+      <!-- 滚动到底部icon -->
+      <div v-if="showScrollButton" class="scroll-to-bottom-btn" @click="scrollToBottom">
+        <span class="font_family icon-icon_movedown_b"></span>
+      </div>
     </div>
 
-    <div class="footer-input" :class="[isFooterInputFocus ? 'focus-style' : '']">
-      <AutoResizeTextarea
-        v-model="userQuestion"
-        :placeholder="'Type your question here...'"
-        @focus="isFooterInputFocus = true"
-        @blur="isFooterInputFocus = false"
-      />
-      <div
-        class="input-icon"
-        :class="[userQuestion ? 'input-style' : 'disable']"
-        @click="handleSend"
-      >
-        <span class="font_family icon-icon_send_b"></span>
+    <div
+      class="footer"
+      :class="[isFooterInputFocus ? 'focus-style' : '', isShowFooterShadow ? 'box-shadow' : '']"
+    >
+      <div class="footer-input">
+        <AutoResizeTextarea
+          style="flex: 1"
+          v-model="userQuestion"
+          :placeholder="'Type your question here...'"
+          @focus="isFooterInputFocus = true"
+          @blur="isFooterInputFocus = false"
+        />
+        <el-button @click="simulateStreamingMarkdown">测试</el-button>
+        <div
+          class="input-icon"
+          :class="[!userQuestion || queryTime !== -1 ? 'disable' : '']"
+          @click="handleSend(userQuestion, false)"
+        >
+          <span class="font_family icon-icon_send_b"></span>
+        </div>
       </div>
     </div>
   </div>
@@ -250,18 +378,23 @@ const handleClose = () => {
   background-color: var(--color-dialog-body-bg);
   overflow: hidden;
   .top-section {
-    height: 150px;
+    position: relative;
+    padding-bottom: 16px;
     background: linear-gradient(
       to bottom,
       var(--color-ai-chat-header-bg-gradient-begin) 10%,
       var(--color-ai-chat-header-bg-gradient-begin) 10%,
       var(--color-ai-chat-header-bg-gradient-end) 100%
     );
+    &.box-shadow {
+      box-shadow: -2px 0px 12px rgba(0, 0, 0, 0.1);
+    }
     .header {
       display: flex;
       justify-content: space-between;
       align-items: center;
       height: 64px;
+      margin-bottom: 6px;
       padding: 0 16px;
       .welcome {
         font-size: 18px;
@@ -280,13 +413,45 @@ const handleClose = () => {
         }
       }
     }
+    .warning-tips {
+      position: absolute;
+      top: 60px;
+      left: 17px;
+      width: calc(100% - 32px);
+      height: 40px;
+      padding-top: 14px;
+      background-color: var(--color-warning-tips-bg);
+      border-radius: 6px;
+      text-align: center;
+      .warning-bg {
+        position: relative;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        height: 12px;
+        width: 12px;
+        margin-right: 6px;
+        border-radius: 50%;
+        background-color: #fff;
+      }
+      .warning-icon {
+        position: absolute;
+        top: -3px;
+        border-radius: 50%;
+        border: none;
+      }
+      span {
+        color: #edb82f;
+      }
+    }
   }
+
   .chat-messages {
     flex: 1;
     display: flex;
     flex-direction: column;
     gap: 16px;
-    padding: 0 16px;
+    padding: 0 16px 18px;
     overflow: auto;
     .message-item {
       position: relative;
@@ -306,20 +471,30 @@ const handleClose = () => {
         height: 30px;
         margin-top: 10px;
         padding-left: 30px;
-        padding-top: 10px;
+        padding-top: 5px;
 
         button.el-button + .el-button {
           margin-left: 0px;
         }
       }
-      .review-input-card {
-        margin-top: 6px;
-        padding: 8px;
-        text-align: right;
-        box-shadow: 1px 1px 12px 0px rgba(0, 0, 0, 0.05);
-        border-radius: 6px;
+      .pause-bg {
+        position: relative;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        height: 12px;
+        width: 12px;
+        margin-right: 6px;
+        border-radius: 50%;
+        background-color: #fff;
+      }
+      .pause-icon {
+        position: absolute;
+        top: -2px;
+        color: #c9353f;
+        border-radius: 50%;
+        border: none;
       }
-
       .el-button--text {
         height: 16px;
         width: 16px;
@@ -338,10 +513,12 @@ const handleClose = () => {
         width: 16px;
         height: 16px;
         margin-top: -1px;
-        margin-right: 2px;
+        margin-right: 4px;
         animation: loading-rotate 2s linear infinite;
       }
-
+      .markdown-body {
+        background: transparent;
+      }
       .pause-btn {
         position: absolute;
         right: -22px;
@@ -362,7 +539,9 @@ const handleClose = () => {
       }
     }
     .query-style {
-      color: #b5b9bf;
+      span {
+        color: #b5b9bf;
+      }
     }
     .robot-bubble {
       background: var(--scoring-bg-color);
@@ -386,6 +565,50 @@ const handleClose = () => {
         bottom: -7px;
       }
     }
+    .scroll-to-bottom-btn {
+      position: absolute;
+      right: 50%;
+      transform: translateX(50%);
+      bottom: 58px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      height: 32px;
+      width: 32px;
+      border-radius: 50%;
+      background-color: #f5f4f4;
+      box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.1);
+      span {
+        color: #2b2f36;
+      }
+      cursor: pointer;
+      &:hover {
+        background-color: var(--color-theme);
+        span {
+          color: #fff;
+        }
+      }
+    }
+  }
+  .footer {
+    padding: 12px 16px;
+    &.box-shadow {
+      box-shadow: 0px 2px 12px rgba(0, 0, 0, 0.1);
+    }
+    &.focus-style {
+      .footer-input {
+        border: 1px solid var(--color-theme);
+      }
+      .input-icon {
+        background-color: var(--color-theme);
+        span {
+          color: #fff;
+        }
+        &:hover {
+          background-color: #d56200;
+        }
+      }
+    }
   }
   .footer-input {
     display: flex;
@@ -393,10 +616,10 @@ const handleClose = () => {
     gap: 12px;
     padding: 4px 12px;
     padding-right: 4px;
-    margin: 12px 16px;
     border: 1px solid var(--input-border);
     border-radius: 20px;
     box-sizing: border-box;
+
     .input-icon {
       display: flex;
       justify-content: center;
@@ -410,39 +633,8 @@ const handleClose = () => {
         cursor: not-allowed;
       }
     }
-    .input-style {
-      background-color: var(--color-theme);
-      span {
-        color: #fff;
-      }
-      &:hover {
-        background-color: #d56200;
-      }
-    }
-    &.focus-style {
-      border: 1px solid var(--color-theme);
-    }
   }
 
-  // .input-area {
-  //   width: 100%;
-  //   font-size: 14px;
-  //   line-height: 21px;
-  //   padding: 4px;
-  //   resize: none;
-  //   overflow-y: hidden; // 默认不显示滚动条
-  //   height: 40px; // 初始高度(1 行)
-  //   max-height: 100px; // 最多 4 行
-  //   box-sizing: border-box;
-  //   border: none;
-  //   outline-color: #fff;
-  //   border-radius: 8px;
-  //   transition: height 0.1s ease;
-  //   &::placeholder {
-  //     color: #b5b9bf;
-  //     opacity: 1;
-  //   }
-  // }
   @keyframes loading-rotate {
     0% {
       transform: rotate(0deg);

+ 96 - 75
src/views/AIRobotChat/src/components/AIQuestions.vue

@@ -37,129 +37,150 @@ const DeQuestions = ref([
     isLong: false
   },
   {
-    label: 'List shipments with ETA changes in the last 30 days11111111111111111111111111111111111.',
-    value: 'List shipments with ETA changes in the last 30 days11111111111111111111111111111111111.',
+    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.',
+    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.',
+    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 itemGroups = ref([])
+const pages = ref([])
 // 随机显示方法
 const prepareGroups = () => {
-  const groups = [];
-  let currentGroup = [];
-  let currentHeight = 0;
+  const groups = []
+  let currentGroup = []
+  let currentHeight = 0
+
+  DeQuestions.value.forEach((item) => {
+    const itemHeight = item.isLong ? 2 : 1
 
-  DeQuestions.value.forEach(item => {
-    const itemHeight = item.isLong ? 2 : 1;
-    
     if (currentHeight + itemHeight > 4) {
-      groups.push(currentGroup);
-      currentGroup = [];
-      currentHeight = 0;
+      groups.push(currentGroup)
+      currentGroup = []
+      currentHeight = 0
     }
-    
-    currentGroup.push(item);
-    currentHeight += itemHeight;
-  });
+
+    currentGroup.push(item)
+    currentHeight += itemHeight
+  })
 
   // 添加最后一组
   if (currentGroup.length > 0) {
-    groups.push(currentGroup);
+    groups.push(currentGroup)
   }
 
-  itemGroups.value = groups;
+  itemGroups.value = groups
 }
 // 智能分页算法
 const generatePages = () => {
-  const result = [];
-  let currentPage = { row1: [], row2: [] };
-  let cursor = 0;
-  
-  while(cursor < DeQuestions.value.length) {
+  const result = []
+  let currentPage = { row1: [], row2: [] }
+  let cursor = 0
+
+  while (cursor < DeQuestions.value.length) {
     // 处理第一排
-    const currentItem = DeQuestions.value[cursor];
+    const currentItem = DeQuestions.value[cursor]
     // 第一排逻辑
-    if(currentItem.isLong) {
+    if (currentItem.isLong) {
       // 长文本独占第一排
-      currentPage.row1.push(currentItem);
-      cursor++;
+      currentPage.row1.push(currentItem)
+      cursor++
     } else {
       // 尝试取两个短文本
-      const nextItem = DeQuestions.value[cursor + 1];
-      if(nextItem && !nextItem.isLong) {
-        currentPage.row1.push(currentItem, nextItem);
-        cursor += 2;
+      const nextItem = DeQuestions.value[cursor + 1]
+      if (nextItem && !nextItem.isLong) {
+        currentPage.row1.push(currentItem, nextItem)
+        cursor += 2
       } else {
-        currentPage.row1.push(currentItem);
-        cursor++;
+        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++;
+    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;
+        const nextSecondItem = remaining[1]
+        if (nextSecondItem && !nextSecondItem.isLong) {
+          currentPage.row2.push(secondItem, nextSecondItem)
+          cursor += 2
         } else {
-          currentPage.row2.push(secondItem);
-          cursor++;
+          currentPage.row2.push(secondItem)
+          cursor++
         }
       }
     }
 
     // 保存当前页
-    result.push(currentPage);
-    
+    result.push(currentPage)
+
     // 重置页面
-    currentPage = { row1: [], row2: [] };
+    currentPage = { row1: [], row2: [] }
   }
-  pages.value = result.filter(p => p.row1.length > 0);
+  pages.value = result.filter((p) => p.row1.length > 0)
   console.log(pages.value)
 }
 onMounted(() => {
   prepareGroups()
   generatePages()
-});
+})
+
+const emit = defineEmits<{ question: [string] }>()
+const clickQuestion = (question) => {
+  emit('question', question)
+}
 </script>
 <template>
   <div class="flex_center">
-    <div class="dialogue_content ">
+    <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" />
+          <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"
-        >
+      <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 }">
+                  <div
+                    @click="clickQuestion(item.label)"
+                    class="dialogue_content_item"
+                    :class="{ 'long-item': item.isLong }"
+                  >
                     {{ item.label }}
                   </div>
                 </template>
@@ -167,7 +188,11 @@ onMounted(() => {
               <!-- 第二排 -->
               <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 }">
+                  <div
+                    @click="clickQuestion(item.label)"
+                    class="dialogue_content_item"
+                    :class="{ 'long-item': item.isLong }"
+                  >
                     {{ item.label }}
                   </div>
                 </template>
@@ -176,17 +201,14 @@ onMounted(() => {
           </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"
-        >
+      <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 
+            <div
               class="dialogue_content_item"
               v-for="item in group"
               :key="item.label"
-              :class="{ 'long_item': item.isLong }"
+              :class="{ long_item: item.isLong }"
             >
               {{ item.label }}
             </div>
@@ -206,7 +228,7 @@ onMounted(() => {
 }
 .dialogue_title_left_img {
   position: absolute;
-  top: -22px;
+  top: -20px;
 }
 .dialogue_title_left_text {
   background: var(--color-dialogue_title);
@@ -260,4 +282,3 @@ onMounted(() => {
   padding-bottom: 0 !important;
 }
 </style>
-

+ 46 - 0
src/views/AIRobotChat/src/components/LoadingDots.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="loading-dots">
+    <span class="dot"></span>
+    <span class="dot"></span>
+    <span class="dot"></span>
+  </div>
+</template>
+
+<style scoped>
+.loading-dots {
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  gap: 6px;
+  height: 24px;
+}
+
+.dot {
+  width: 4px;
+  height: 4px;
+  border-radius: 50%;
+  background-color: var(--color-theme);
+  animation: bounce 1.4s infinite ease-in-out both;
+}
+
+.dot:nth-child(1) {
+  animation-delay: 0s;
+}
+.dot:nth-child(2) {
+  animation-delay: 0.2s;
+}
+.dot:nth-child(3) {
+  animation-delay: 0.4s;
+}
+
+@keyframes bounce {
+  0%,
+  80%,
+  100% {
+    transform: translateY(0);
+  }
+  40% {
+    transform: translateY(-4px);
+  }
+}
+</style>

+ 0 - 1
src/views/Layout/src/LayoutView.vue

@@ -45,7 +45,6 @@ const AvatarClick = () => {
 
     <!-- 右侧整体布局 -->
     <el-container style="min-width: 900px">
-      <el-button @click="onClick">测试</el-button>
       <!-- 顶部Header -->
       <el-header class="layout-header">
         <Header></Header>

+ 0 - 3
src/views/Layout/src/components/Header/components/NotificationDrawer.vue

@@ -149,7 +149,6 @@ div.layout-toolbar {
     background-color: var(--color-dialog-body-bg);
   }
   .password-notifications {
-    margin-bottom: 16px;
     .header {
       display: flex;
       align-items: center;
@@ -187,8 +186,6 @@ div.layout-toolbar {
     }
   }
   .feature-update {
-    margin-bottom: 16px;
-
     .header {
       display: flex;
       align-items: center;

+ 6 - 0
src/views/Layout/src/components/Header/components/TrainingCard.vue

@@ -164,5 +164,11 @@ const closeMessage = () => {
   :deep(.notification-card) {
     margin-bottom: 0px;
   }
+  :deep(.feature-update) {
+    margin-bottom: 0px;
+  }
+  :deep(.password-notifications) {
+    margin-bottom: 0px;
+  }
 }
 </style>

+ 2 - 2
src/views/Login/src/components/ChangePasswordCard.vue

@@ -229,7 +229,7 @@ const checkPassword = () => {
       </div>
       <template #footer>
         <div class="license">
-          <span>© KERRY LOGISTICS NETWORK LIMITED. ALL RIGHTS RESERVED.</span>
+          <span>© KLN LOGISTICS GROUP LIMITED. ALL RIGHTS RESERVED.</span>
         </div>
       </template>
     </el-card>
@@ -253,7 +253,7 @@ const checkPassword = () => {
 }
 
 .login-card {
-  width: 410px;
+  width: 400px;
   border-radius: 12px;
   .title {
     display: flex;

+ 3 - 3
src/views/Login/src/loginView.vue

@@ -394,7 +394,7 @@ const firstLoginTipsRef = ref()
       </div>
       <template #footer>
         <div class="license">
-          <span>© KERRY LOGISTICS NETWORK LIMITED. ALL RIGHTS RESERVED.</span>
+          <span>© KLN LOGISTICS GROUP LIMITED. ALL RIGHTS RESERVED.</span>
         </div>
       </template>
     </el-card>
@@ -449,7 +449,7 @@ const firstLoginTipsRef = ref()
       </div>
       <template #footer>
         <div class="license">
-          <span>© KERRY LOGISTICS NETWORK LIMITED. ALL RIGHTS RESERVED.</span>
+          <span>© KLN LOGISTICS GROUP LIMITED. ALL RIGHTS RESERVED.</span>
         </div>
       </template>
     </el-card>
@@ -487,7 +487,7 @@ const firstLoginTipsRef = ref()
 }
 
 .login-card {
-  width: 410px;
+  width: 400px;
 
   .card-title {
     display: flex;

+ 7 - 2
src/views/SystemMessage/src/SystemMessage.vue

@@ -192,7 +192,7 @@ onMounted(() => {
                 <span>{{ handleCount(unreadNotificationList.length) }}</span>
               </div>
             </template>
-            <div style="padding: 10px 140px 0px 16px" v-if="activeTabName === 'Unread'">
+            <div style="padding-bottom: 20px" v-if="activeTabName === 'Unread'">
               <NotificationMessageCard
                 v-if="activeTabName === 'Unread'"
                 :data="unreadNotificationList"
@@ -203,7 +203,7 @@ onMounted(() => {
           </el-tab-pane>
           <el-tab-pane label="Read" name="Read">
             <template #label><span style="margin-right: 4px">Read</span> </template>
-            <div style="padding: 10px 140px 0px 16px" v-if="activeTabName === 'Read'">
+            <div style="padding-bottom: 20px" v-if="activeTabName === 'Read'">
               <NotificationMessageCard
                 v-if="activeTabName === 'Read'"
                 :updateReadCardsOnChange="false"
@@ -322,5 +322,10 @@ onMounted(() => {
       height: 100%;
     }
   }
+  :deep {
+    .scroller {
+      padding-top: 10px;
+    }
+  }
 }
 </style>

+ 1 - 0
src/views/Tracking/src/TrackingView.vue

@@ -259,6 +259,7 @@ const DateRangeSearch = (val: any, value: any) => {
 }
 //MoreFiltersSearch
 const MoreFiltersSearch = (val: any, value: any) => {
+  console.log(val,value)
   filterData.morefiltersData = []
   if (Object.keys(value).length == 0) {
     delete searchTableQeuryTracking.shipper

+ 5 - 5
src/views/Tracking/src/components/TrackingDetail/src/TrackingDetail.vue

@@ -146,11 +146,11 @@ const SubscribeShipments = () => {
 <template>
   <div class="tracking-detail" v-vloading="loading">
     <!-- 分享链接 -->
-    <div class="share-link" @click="openShareDialog">
-      <el-tooltip content="Share" :offset="4">
+    <el-tooltip content="Share" :offset="4" placement="top">
+      <div class="share-link" @click="openShareDialog">
         <span class="font_family icon-icon_share_b share-icon"></span>
-      </el-tooltip>
-    </div>
+      </div>
+    </el-tooltip>
     <div class="header" :class="{ 'is-dark': themeStore.theme === 'dark' }">
       <div class="detail-status">
         <span
@@ -497,7 +497,7 @@ const SubscribeShipments = () => {
   }
   .share-link {
     position: fixed;
-    bottom: 152px;
+    bottom: 248px;
     right: 20px;
     display: flex;
     align-items: center;

+ 1 - 1
vite.config.ts

@@ -8,7 +8,7 @@ import Icons from 'unplugin-icons/vite'
 import IconsResolver from 'unplugin-icons/resolver'
 
 // https://vitejs.dev/config/
-export default defineConfig((mode) => {
+export default defineConfig(({ mode }) => {
   const env = loadEnv(mode, process.cwd())
   return {
     base: env.VITE_BASE_URL || '/',

Неке датотеке нису приказане због велике количине промена