yangg 12 小时之前
父节点
当前提交
08c8aba644
共有 2 个文件被更改,包括 408 次插入1 次删除
  1. 1 0
      index.html
  2. 407 1
      src/views/report.vue

+ 1 - 0
index.html

@@ -4,6 +4,7 @@
     <meta charset="UTF-8" />
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
     <title></title>
   </head>
   <body>

+ 407 - 1
src/views/report.vue

@@ -630,6 +630,32 @@
           </button>
         </div>
       </div>
+
+       <!-- 浮动操作按钮 (FAB) -->
+       <div class="fab" :class="{ 'open': isFabOpen }" @click="toggleFab">
+        <!-- 初始状态:显示三个点 -->
+        <div class="fab-dots" v-if="!isFabOpen">
+          <span></span>
+          <span></span>
+          <span></span>
+        </div>
+        
+        <!-- 展开状态:显示三个选项按钮 -->
+        <div class="option reply" :class="{ 'open': isFabOpen }" @click.stop="handleReply">
+          <i class="material-icons">reply</i>
+        </div>
+        <div class="option love" :class="{ 'open': isFabOpen }" @click.stop="handleLove">
+          <i class="material-icons">favorite</i>
+        </div>
+        <div class="option delete" :class="{ 'open': isFabOpen }" @click.stop="handleDelete">
+          <i class="material-icons">delete</i>
+        </div>
+        
+        <!-- 关闭按钮:只在展开状态显示 -->
+        <div class="close" v-if="isFabOpen" @click.stop="toggleFab">
+          <i class="material-icons">close</i>
+        </div>
+      </div>
     </div>
 
     <div  
@@ -983,6 +1009,9 @@ const isMobile = ref(false);
 
 //知识库搜索
 const enableKnowledgeSearch = ref(false);
+
+// FAB 浮动操作按钮状态
+const isFabOpen = ref(false);
 const knowledgeResults = ref([]);
 const knowledgeTotal = ref(0);
 const isKnowledgeLoading = ref(false);
@@ -2676,6 +2705,35 @@ const updateCollapsedState = () => {
     applyTheme(isDarkTheme.value);
   };
 
+  // FAB 浮动操作按钮相关方法
+  const toggleFab = () => {
+    // 添加动画类来启用transition
+    const fabElement = document.querySelector('.fab');
+    if (fabElement) {
+      fabElement.classList.add('animated');
+    }
+    
+    isFabOpen.value = !isFabOpen.value;
+  };
+
+  const handleReply = () => {
+    console.log('回复功能');
+    // 这里可以添加回复逻辑
+    message.info('回复功能');
+  };
+
+  const handleLove = () => {
+    console.log('收藏功能');
+    // 这里可以添加收藏逻辑
+    message.info('收藏功能');
+  };
+
+  const handleDelete = () => {
+    console.log('删除功能');
+    // 这里可以添加删除逻辑
+    message.info('删除功能');
+  };
+
   // 初始化主题
   onMounted(() => {
     if (document.querySelector('.chat')) {
@@ -2978,7 +3036,6 @@ const updateCollapsedState = () => {
   bottom: 0;
   justify-content: space-between;
   background-color: var(--primary-bg);
- 
  /*  margin: 16px; */
   border-radius: 16px;
   box-shadow:  0 10px 15px rgba(0, 0, 0, 0.1);
@@ -6356,4 +6413,353 @@ button,
   background: #e8f5e8;
 }
 
+/* FAB 浮动操作按钮样式 */
+.fab {
+  position: fixed;
+  bottom: 22%;
+  right: 29%;
+  width: 50px;
+  height: 50px;
+  background: #ffd700;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  z-index: 1000;
+}
+
+.fab:hover {
+  transform: scale(1.1);
+  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
+}
+
+/* 三个点的样式 */
+.fab .fab-dots {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 4px;
+  width: 100%;
+  height: 100%;
+}
+
+.fab .fab-dots span {
+  width: 6px;
+  height: 6px;
+  background: #FFF;
+  border-radius: 50%;
+  transition: all 0.3s ease;
+}
+
+.fab .close {
+  position: absolute;
+  width: 40px;
+  height: 40px;
+/*   background: #ffd700; */
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  opacity: 1;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  top: 10px;
+  left: 50%;
+  transform: translateX(-50%);
+ /*  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); */
+}
+
+.fab .close i {
+  font-size: 20px;
+  color: #FFF;
+  transition: transform 0.3s ease;
+}
+
+.fab .option {
+  position: absolute;
+  width: 40px;
+  height: 40px;
+  background: #6a4c93;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  opacity: 0;
+  transform: scale(0) translate(0, 0);
+  transition: none; /* 初始化时不使用transition */
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+  /* 初始位置在中心 */
+  top: 50%;
+  left: 50%;
+  margin-top: -20px;
+  margin-left: -20px;
+}
+
+/* 当FAB有动画类时启用transition */
+.fab.animated .option {
+  transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+}
+
+.fab .option i {
+  font-size: 20px;
+  color: white;
+}
+
+/* .fab .option:hover {
+  transform: scale(1.1);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
+} */
+
+.open{
+  width: 40px;
+  height: 40px;
+  
+}
+
+.open .close{
+  top: 0;
+}
+
+/* 展开状态 */
+.fab.open .close {
+  opacity: 1;
+  transform: translateX(-50%) scale(1);
+  animation: fadeInClose 0.3s ease-out 0.2s both;
+}
+
+.fab.open .option {
+  opacity: 1;
+  transform: scale(1);
+}
+
+.fab.open .option.reply {
+  transform: translate(-60px, -25px) scale(1);
+  animation: expandReply 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.1s both;
+}
+
+.fab.open .option.love {
+  transform: translate(0px, -60px) scale(1);
+  animation: expandLove 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.2s both;
+}
+
+.fab.open .option.delete {
+  transform: translate(55px, -25px) scale(1);
+  animation: expandDelete 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.3s both;
+}
+
+/* 收缩动画 - 从外向内收缩 */
+.fab:not(.open) .option.reply {
+  animation: collapseReply 0.4s cubic-bezier(0.55, 0.055, 0.675, 0.19) both;
+}
+
+.fab:not(.open) .option.love {
+  animation: collapseLove 0.4s cubic-bezier(0.55, 0.055, 0.675, 0.19) 0.1s both;
+}
+
+.fab:not(.open) .option.delete {
+  animation: collapseDelete 0.4s cubic-bezier(0.55, 0.055, 0.675, 0.19) both;
+}
+
+/* 动画关键帧定义 */
+@keyframes expandReply {
+  0% {
+    transform: scale(0) translate(0, 0);
+    opacity: 0;
+  }
+  20% {
+    transform: scale(0.3) translate(-12px, -5px);
+    opacity: 0.5;
+  }
+  40% {
+    transform: scale(0.6) translate(-24px, -10px);
+    opacity: 0.7;
+  }
+  60% {
+    transform: scale(0.8) translate(-36px, -15px);
+    opacity: 0.9;
+  }
+  80% {
+    transform: scale(0.95) translate(-48px, -20px);
+    opacity: 1;
+  }
+  100% {
+    transform: scale(1) translate(-60px, -25px);
+    opacity: 1;
+  }
+}
+
+@keyframes expandLove {
+  0% {
+    transform: scale(0) translate(0, 0);
+    opacity: 0;
+  }
+  20% {
+    transform: scale(0.3) translate(0, -12px);
+    opacity: 0.5;
+  }
+  40% {
+    transform: scale(0.6) translate(0, -24px);
+    opacity: 0.7;
+  }
+  60% {
+    transform: scale(0.8) translate(0, -36px);
+    opacity: 0.9;
+  }
+  80% {
+    transform: scale(0.95) translate(0, -48px);
+    opacity: 1;
+  }
+  100% {
+    transform: scale(1) translate(0, -60px);
+    opacity: 1;
+  }
+}
+
+@keyframes expandDelete {
+  0% {
+    transform: scale(0) translate(0, 0);
+    opacity: 0;
+  }
+  20% {
+    transform: scale(0.3) translate(11px, -5px);
+    opacity: 0.5;
+  }
+  40% {
+    transform: scale(0.6) translate(22px, -10px);
+    opacity: 0.7;
+  }
+  60% {
+    transform: scale(0.8) translate(33px, -15px);
+    opacity: 0.9;
+  }
+  80% {
+    transform: scale(0.95) translate(44px, -20px);
+    opacity: 1;
+  }
+  100% {
+    transform: scale(1) translate(55px, -25px);
+    opacity: 1;
+  }
+}
+
+@keyframes collapseReply {
+  0% {
+    transform: translate(-60px, -25px) scale(1);
+    opacity: 1;
+  }
+  20% {
+    transform: translate(-48px, -20px) scale(0.95);
+    opacity: 0.9;
+  }
+  40% {
+    transform: translate(-36px, -15px) scale(0.8);
+    opacity: 0.7;
+  }
+  60% {
+    transform: translate(-24px, -10px) scale(0.6);
+    opacity: 0.5;
+  }
+  80% {
+    transform: translate(-12px, -5px) scale(0.3);
+    opacity: 0.3;
+  }
+  100% {
+    transform: translate(0, 0) scale(0);
+    opacity: 0;
+  }
+}
+
+@keyframes collapseLove {
+  0% {
+    transform: translate(0px, -60px) scale(1);
+    opacity: 1;
+  }
+  20% {
+    transform: translate(0px, -48px) scale(0.95);
+    opacity: 0.9;
+  }
+  40% {
+    transform: translate(0px, -36px) scale(0.8);
+    opacity: 0.7;
+  }
+  60% {
+    transform: translate(0px, -24px) scale(0.6);
+    opacity: 0.5;
+  }
+  80% {
+    transform: translate(0px, -12px) scale(0.3);
+    opacity: 0.3;
+  }
+  100% {
+    transform: translate(0, 0) scale(0);
+    opacity: 0;
+  }
+}
+
+@keyframes collapseDelete {
+  0% {
+    transform: translate(55px, -25px) scale(1);
+    opacity: 1;
+  }
+  20% {
+    transform: translate(44px, -20px) scale(0.95);
+    opacity: 0.9;
+  }
+  40% {
+    transform: translate(33px, -15px) scale(0.8);
+    opacity: 0.7;
+  }
+  60% {
+    transform: translate(22px, -10px) scale(0.6);
+    opacity: 0.5;
+  }
+  80% {
+    transform: translate(11px, -5px) scale(0.3);
+    opacity: 0.3;
+  }
+  100% {
+    transform: translate(0, 0) scale(0);
+    opacity: 0;
+  }
+}
+
+@keyframes fadeInClose {
+  0% {
+    transform: translateX(-50%) scale(0);
+    opacity: 0;
+  }
+  100% {
+    transform: translateX(-50%) scale(1);
+    opacity: 1;
+  }
+}
+
+/* 深色主题适配 */
+.dark-theme .fab {
+  background: #ffd700;
+}
+
+.dark-theme .fab .option {
+  background: #8b5cf6;
+}
+
+.dark-theme .fab .close i {
+  color: #333;
+}
+
+/* .fab .option.reply {
+    left: calc((100% / 3) - 3px);
+}
+
+.fab .option.love {
+    left: calc(50% - 3px);
+}
+
+.fab .option.delete {
+    left: calc(((100% / 3) * 2) - 3px);
+}
+ */
 </style>