|
@@ -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>
|